A ESLint plugin to lint and fix inline scripts contained in HTML files.
npx @tessl/cli install tessl/npm-eslint-plugin-html@8.1.0ESLint Plugin HTML enables linting and fixing of JavaScript code embedded within HTML script tags, providing developers with the ability to apply ESLint rules to inline JavaScript in HTML files. It intelligently parses HTML and XML documents, extracts JavaScript content from script tags, and applies ESLint analysis while maintaining proper scope handling across multiple script tags within the same file.
npm install --save-dev eslint-plugin-htmlESLint 9+ (flat config):
import html from "eslint-plugin-html";ESLint 8 and below:
const html = require("eslint-plugin-html");Note: This plugin automatically patches ESLint's internal verification methods when imported, so no explicit API calls are needed. The main export is an empty object ({}) - functionality is provided through the automatic patching mechanism.
import html from "eslint-plugin-html";
export default [
{
files: ["**/*.html"],
plugins: { html },
settings: {
"html/html-extensions": [".html", ".htm"],
"html/indent": "+2"
}
}
];{
"plugins": ["html"],
"settings": {
"html/html-extensions": [".html", ".htm"],
"html/indent": "+2"
}
}<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<script>
// This JavaScript will be linted by ESLint
const greeting = "Hello, World!";
console.log(greeting);
</script>
<!-- eslint-disable-next-script -->
<script>
// This script will be ignored by ESLint
var templateCode = <%= someTemplateVariable %>;
</script>
</body>
</html>ESLint Plugin HTML works by:
The plugin works by automatically discovering and patching ESLint's internal modules when imported. This happens through runtime modification of ESLint's verification functions.
// Core patching functions (internal)
function iterateESLintModules(fn: Function): void;
function patch(module: Object): void;
function getLinterFromModule(module: Object): Function | undefined;// ESLint legacy mode patching (ESLint 6-8)
function createVerifyPatch(verify: Function): Function;
// ESLint flat config mode patching (ESLint 9+)
function createVerifyWithFlatConfigPatch(
eslintModule: Object,
verifyWithFlatConfig: Function
): Function;The patching mechanism searches for ESLint modules in the require cache and modifies their verification methods to process HTML files before standard JavaScript linting.
The plugin provides several core functions for HTML parsing, JavaScript extraction, and message handling.
/**
* Iterates through HTML content and identifies script chunks
* @param code - HTML source code
* @param xmlMode - Whether to parse in XML mode
* @param options - Plugin settings object
* @param onChunk - Callback function for each HTML/script chunk
*/
function iterateScripts(
code: string,
xmlMode: boolean,
options: PluginSettings,
onChunk: (chunk: Chunk) => void
): void;
interface Chunk {
type: "html" | "script";
start: number;
end: number;
cdata: Array<{ start: number; end: number }>;
}/**
* Handles integration with external HTML plugins like @html-eslint/parser
* @param config - ESLint configuration object
* @param callOriginalVerify - Function to call original ESLint verify
* @returns Array containing messages and modified config
*/
function verifyExternalHtmlPlugin(
config: Object,
callOriginalVerify: Function
): [Object[], Object];Control ESLint behavior using HTML comments to disable/enable linting for specific script tags.
<!-- eslint-disable -->
<!-- eslint-enable -->
<!-- eslint-disable-next-script -->Usage Examples:
<!-- Disable ESLint for all following scripts until re-enabled -->
<!-- eslint-disable -->
<script>
var foo = 1; // This won't be linted
</script>
<!-- eslint-enable -->
<!-- Disable ESLint for only the next script tag -->
<!-- eslint-disable-next-script -->
<script>
var template = <%= serverVariable %>; // Template syntax ignored
</script>
<script>
const normalCode = "This will be linted"; // This will be linted normally
</script>Configure the plugin behavior through ESLint settings under the html namespace.
// HTML comment processing states (from src/extract.js)
const NO_IGNORE = 0;
const IGNORE_NEXT = 1;
const IGNORE_UNTIL_ENABLE = 2;
// Internal rule names for plugin processing
const PREPARE_RULE_NAME = "__eslint-plugin-html-prepare";
const PREPARE_PLUGIN_NAME = "__eslint-plugin-html-prepare";// Settings object properties
"html/html-extensions": string[] // HTML file extensions (default: [".erb", ".handlebars", ".hbs", ".htm", ".html", ".mustache", ".nunjucks", ".php", ".tag", ".riot", ".twig", ".we"])
"html/xml-extensions": string[] // XML file extensions (default: [".xhtml", ".xml"])// Settings object properties
"html/javascript-tag-names": string[] // Tag names to treat as JavaScript containers (default: ["script"])
"html/javascript-mime-types": (string | RegExp)[] // MIME types to identify JavaScript content
"html/ignore-tags-without-type": boolean // Ignore script tags without type attribute (default: false)// Settings object properties
"html/indent": string // Indentation pattern (e.g., "4", "tab", "+2")
"html/report-bad-indent": number | string // Report indentation errors: 0/"off", 1/"warn", 2/"error"Usage Examples:
// ESLint 9+ flat config
export default [
{
files: ["**/*.html", "**/*.php"],
plugins: { html },
settings: {
"html/html-extensions": [".html", ".php", ".erb"],
"html/javascript-tag-names": ["script", "customscript"],
"html/javascript-mime-types": ["text/javascript", "/^text\\/(javascript|jsx)$/"],
"html/indent": "+2", // Relative to script tag indentation
"html/report-bad-indent": "error",
"html/ignore-tags-without-type": false
}
}
];Control how variables are shared between multiple script tags in the same HTML file.
// ESLint parser options
{
languageOptions: {
sourceType: "script" | "module" // "script" shares scopes, "module" isolates them
}
}Script Scope Examples:
<!-- With sourceType: "script" (default) - scopes are shared -->
<script>
var globalVar = "shared";
</script>
<script>
console.log(globalVar); // ✓ Valid - variable is accessible
</script>
<!-- With sourceType: "module" - each script has isolated scope -->
<script>
var moduleVar = "isolated"; // ⚠ no-unused-vars warning
</script>
<script>
console.log(moduleVar); // ❌ no-undef error
</script>The plugin exposes internal processing functions for advanced use cases, though these are primarily used internally.
// From src/settings.js
function getSettings(settings: object): PluginSettings;
interface PluginSettings {
htmlExtensions: string[];
xmlExtensions: string[];
javaScriptTagNames: string[];
indent: { relative: boolean; spaces: string } | null;
reportBadIndent: number;
isJavaScriptMIMEType(type: string): boolean;
ignoreTagsWithoutType: boolean;
}// From src/extract.js
function extract(code: string, xmlMode: boolean, options: PluginSettings): ExtractionResult;
interface ExtractionResult {
code: TransformableString[];
badIndentationLines: number[];
hasBOM: boolean;
}// From src/TransformableString.js
class TransformableString {
constructor(original: string);
toString(): string;
replace(from: number, to: number, str: string): void;
originalIndex(index: number): number | undefined;
originalLocation(location: object): object | undefined;
getOriginalLine(n: number): string;
}// From src/getFileMode.js
function getFileMode(pluginSettings: PluginSettings, filenameOrOptions: string | object): "html" | "xml" | undefined;// From src/remapMessages.js
const { remapMessages } = require("./remapMessages");
function remapMessages(messages: object[], hasBOM: boolean, codePart: TransformableString): object[];// From src/utils.js
const { oneLine, splatSet } = require("./utils");
/**
* Formats template literal strings to single line by joining and trimming
* @param parts - Template literal parts array (also uses arguments object)
* @returns Single line formatted string
*/
function oneLine(parts: TemplateStringsArray): string;
/**
* Flattens nested arrays/sets into a single Set
* @param items - Items to flatten (can be arrays, sets, or individual items)
* @returns Flattened Set containing all items
*/
function splatSet(items: any): Set<any>;
// Additional utility functions from src/settings.js
/**
* Filters items from array that exist in exclude array
* @param array - Source array to filter
* @param excludeArray - Array of items to exclude
* @returns Filtered array without excluded items
*/
function filterOut(array: any[], excludeArray?: any[]): any[];
/**
* Compiles a string representation of a regex into a RegExp object
* @param re - String regex in format "/pattern/flags"
* @returns Compiled RegExp object
*/
function compileRegExp(re: string): RegExp;
/**
* Gets a setting value from nested or flat settings object
* @param settings - ESLint settings object
* @param name - Setting name to retrieve
* @returns Setting value or undefined
*/
function getSetting(settings: object, name: string): any;// From src/verifyWithSharedScopes.js
const { verifyWithSharedScopes } = require("./verifyWithSharedScopes");
/**
* Processes multiple script tags with shared variable scopes
* @param codeParts - Array of TransformableString instances
* @param verifyCodePart - Function callback for verifying individual code parts
* @param parserOptions - ESLint parser options
*/
function verifyWithSharedScopes(
codeParts: TransformableString[],
verifyCodePart: Function,
parserOptions: object
): void;// ESLint configuration integration
interface ESLintFlatConfig {
files: string[];
plugins: { html: typeof import("eslint-plugin-html") };
settings?: {
"html/html-extensions"?: string[];
"html/xml-extensions"?: string[];
"html/javascript-tag-names"?: string[];
"html/javascript-mime-types"?: (string | RegExp)[];
"html/ignore-tags-without-type"?: boolean;
"html/indent"?: string;
"html/report-bad-indent"?: number | string;
};
}
// Legacy ESLint configuration
interface ESLintLegacyConfig {
plugins: string[];
settings?: {
[key: string]: any;
};
}Works alongside:
@html-eslint/parser and @html-eslint/eslint-plugin@angular-eslint/template-* rulesSupports linting JavaScript in templates:
eslint-plugin-php-markup for enhanced PHP supporteslint-plugin-vue insteadThe plugin handles various error conditions: