UNPKG

eslint-plugin-html-compat

Version:

ESLint plugin to check HTML element and attribute compatibility using browserslist and @mdn/browser-compat-data

184 lines 8.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const browserslist_1 = require("../utils/browserslist"); const compatibility_1 = require("../utils/compatibility"); const rule = { meta: { type: "problem", docs: { description: "Check HTML element and attribute compatibility with target browsers", category: "Possible Errors", recommended: true, }, fixable: undefined, schema: [ { type: "object", properties: { browserslistConfig: { oneOf: [ { type: "string" }, { type: "array", items: { type: "string" } }, ], }, ignoreBrowsers: { type: "array", items: { type: "string" }, description: "List of browsers to ignore in compatibility checks (e.g., ['ie 11', 'opera_mini'])", }, }, additionalProperties: false, }, ], messages: { incompatibleElement: 'HTML element "{{element}}" is not supported in: {{browsers}}.\n See {{mdnUrl}} for details.', incompatibleAttribute: 'HTML attribute "{{attribute}}" on element "{{element}}" is not supported in: {{browsers}}.\n See {{mdnUrl}} for details.', }, }, create(context) { const options = context.options[0] || {}; const targetBrowsers = options.browserslistConfig ? (0, browserslist_1.getSupportedBrowsers)(options.browserslistConfig) : (0, browserslist_1.parseBrowserslistConfig)(); function checkJSXElement(node) { const elementName = node.openingElement?.name?.name || node.name?.name; if (!elementName) return; const elementResult = (0, compatibility_1.checkHtmlElementCompatibility)(elementName, targetBrowsers, options.ignoreBrowsers); if (!elementResult.isSupported) { context.report({ node, messageId: "incompatibleElement", data: { element: elementName, browsers: elementResult.unsupportedBrowsers.join(", "), mdnUrl: elementResult.mdnUrl || "", }, }); } const attributes = node.openingElement?.attributes || node.attributes; if (attributes) { for (const attr of attributes) { if (attr.type === "JSXAttribute" && attr.name?.name) { const attrName = attr.name.name.toLowerCase(); const jsxOnlyAttributes = [ "classname", "htmlfor", "defaultvalue", "defaultchecked", ]; if (jsxOnlyAttributes.includes(attrName)) { continue; } const attrResult = (0, compatibility_1.checkHtmlAttributeCompatibility)(elementName, attrName, targetBrowsers, options.ignoreBrowsers); if (!attrResult.isSupported) { context.report({ node: attr, messageId: "incompatibleAttribute", data: { attribute: attrName, element: elementName, browsers: attrResult.unsupportedBrowsers.join(", "), mdnUrl: attrResult.mdnUrl || "", }, }); } } } } } function checkHTMLElement(node) { const elementName = node.tagName?.toLowerCase() || node.name?.toLowerCase(); if (!elementName) return; const elementResult = (0, compatibility_1.checkHtmlElementCompatibility)(elementName, targetBrowsers, options.ignoreBrowsers); if (!elementResult.isSupported) { context.report({ node, messageId: "incompatibleElement", data: { element: elementName, browsers: elementResult.unsupportedBrowsers.join(", "), mdnUrl: elementResult.mdnUrl || "", }, }); } // Regular HTML nodes use attributes const attributes = node.attributes; if (attributes) { for (const attr of attributes) { const attrName = attr.name?.toLowerCase(); if (attrName) { const attrResult = (0, compatibility_1.checkHtmlAttributeCompatibility)(elementName, attrName, targetBrowsers, options.ignoreBrowsers); if (!attrResult.isSupported) { context.report({ node: attr, messageId: "incompatibleAttribute", data: { attribute: attrName, element: elementName, browsers: attrResult.unsupportedBrowsers.join(", "), mdnUrl: attrResult.mdnUrl || "", }, }); } } } } } function checkVueElement(node) { // VElement has different structure: node.name for element name const elementName = node.name?.toLowerCase(); if (!elementName) return; const elementResult = (0, compatibility_1.checkHtmlElementCompatibility)(elementName, targetBrowsers, options.ignoreBrowsers); if (!elementResult.isSupported) { context.report({ node, messageId: "incompatibleElement", data: { element: elementName, browsers: elementResult.unsupportedBrowsers.join(", "), mdnUrl: elementResult.mdnUrl || "", }, }); } // VElement attributes are in startTag.attributes const attributes = node.startTag?.attributes; if (attributes) { for (const attr of attributes) { // VAttribute has key.name for attribute name const attrName = attr.key?.name?.toLowerCase(); if (attrName) { // Skip Vue-specific directives (v-, @, :) if (attrName.startsWith('v-') || attrName.startsWith('@') || attrName.startsWith(':')) { continue; } const attrResult = (0, compatibility_1.checkHtmlAttributeCompatibility)(elementName, attrName, targetBrowsers, options.ignoreBrowsers); if (!attrResult.isSupported) { context.report({ node: attr, messageId: "incompatibleAttribute", data: { attribute: attrName, element: elementName, browsers: attrResult.unsupportedBrowsers.join(", "), mdnUrl: attrResult.mdnUrl || "", }, }); } } } } } return { JSXElement: checkJSXElement, HTMLElement: checkHTMLElement, Element: checkHTMLElement, VElement: checkVueElement, SvelteElement: checkHTMLElement, }; }, }; exports.default = rule; //# sourceMappingURL=html-compat.js.map