first commit
This commit is contained in:
+152
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @fileoverview Rule to prevent duplicate imports in CSS.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { CSSRuleDefinition } from "../types.js"
|
||||
* @typedef {"duplicateImport" | "removeDuplicateImportWithModifiers" | "removeDuplicateImportWithoutModifiers"} NoDuplicateKeysMessageIds
|
||||
* @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoDuplicateKeysMessageIds }>} NoDuplicateImportsRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Get the end index of import statement including a following newline if present.
|
||||
* @param {string} text The full text of the source code.
|
||||
* @param {number} end The end index of the import statement.
|
||||
* @returns {number} The end index of the import statement including a following newline.
|
||||
*/
|
||||
function getImportEnd(text, end) {
|
||||
let removeEnd = end;
|
||||
// Remove the node, and also remove a following newline if present
|
||||
if (text[removeEnd] === "\r") {
|
||||
removeEnd += text[removeEnd + 1] === "\n" ? 2 : 1;
|
||||
}
|
||||
else if (text[removeEnd] === "\n" || text[removeEnd] === "\f") {
|
||||
removeEnd += 1;
|
||||
}
|
||||
return removeEnd;
|
||||
}
|
||||
/**
|
||||
* Get the modifiers of an import statement.
|
||||
* @param {Object} importNode The import node to get modifiers from.
|
||||
* @param {Object} sourceCode The source code object.
|
||||
* @returns {string[]} An array of modifiers for the import statement.
|
||||
*/
|
||||
function getImportModifiers(importNode, sourceCode) {
|
||||
const importModifiers = [];
|
||||
const importHasModifiers = importNode.prelude?.children.length > 1;
|
||||
if (importHasModifiers) {
|
||||
importNode.prelude?.children.slice(1).forEach(modifier => {
|
||||
const modifierText = sourceCode.getText(modifier).trim();
|
||||
importModifiers.push(modifierText);
|
||||
});
|
||||
}
|
||||
return importModifiers;
|
||||
}
|
||||
/**
|
||||
* Get the fix for a duplicate import statement.
|
||||
* @param {Object} fixer The fixer object.
|
||||
* @param {string} text The full text of the source code.
|
||||
* @param {number} start The start index of the import statement to fix.
|
||||
* @param {number} end The end index of the import statement to fix.
|
||||
* @param {boolean} hasModifiers A boolean indicating whether the import statement has modifiers that differ from the original import.
|
||||
* @returns {Object|null} A fix object if a fix is applicable, or null if no fix should be applied.
|
||||
*/
|
||||
function getFixForImport(fixer, text, start, end, hasModifiers) {
|
||||
const removeEnd = getImportEnd(text, end);
|
||||
if (hasModifiers) {
|
||||
return fixer.removeRange([start, removeEnd]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule
|
||||
//-----------------------------------------------------------------------------
|
||||
export default /** @satisfies {NoDuplicateImportsRuleDefinition} */ ({
|
||||
meta: {
|
||||
type: "problem",
|
||||
fixable: "code",
|
||||
hasSuggestions: true,
|
||||
docs: {
|
||||
description: "Disallow duplicate @import rules",
|
||||
recommended: true,
|
||||
url: "https://github.com/eslint/css/blob/main/docs/rules/no-duplicate-imports.md",
|
||||
},
|
||||
messages: {
|
||||
duplicateImport: "Unexpected duplicate @import rule for '{{url}}'.",
|
||||
removeDuplicateImportWithModifiers: "Remove duplicate @import rule with modifier(s) - {{modifiers}}.",
|
||||
removeDuplicateImportWithoutModifiers: "Remove duplicate @import rule without modifiers.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const { sourceCode } = context;
|
||||
const imports = [];
|
||||
return {
|
||||
"Atrule[name=/^import$/i]"(node) {
|
||||
const url = node.prelude?.children[0].value;
|
||||
const hasImport = imports.some(importNode => importNode.prelude?.children[0].value === url);
|
||||
if (hasImport) {
|
||||
const firstImportNode = imports.find(importNode => importNode.prelude?.children[0].value === url);
|
||||
const [firstImportStart, firstImportEnd] = sourceCode.getRange(firstImportNode);
|
||||
const firstImportHasModifiers = firstImportNode.prelude?.children.length > 1;
|
||||
const nodeHasModifiers = node.prelude?.children.length > 1;
|
||||
const [start, end] = sourceCode.getRange(node);
|
||||
const text = sourceCode.text;
|
||||
const firstImportModifiers = getImportModifiers(firstImportNode, sourceCode);
|
||||
const duplicateImportModifiers = getImportModifiers(node, sourceCode);
|
||||
const hasSameModifiers = firstImportModifiers.length ===
|
||||
duplicateImportModifiers.length &&
|
||||
firstImportModifiers.every((modifier, index) => modifier === duplicateImportModifiers[index]);
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "duplicateImport",
|
||||
data: { url },
|
||||
fix(fixer) {
|
||||
const hasModifiers = (!firstImportHasModifiers &&
|
||||
!nodeHasModifiers) ||
|
||||
hasSameModifiers;
|
||||
return getFixForImport(fixer, text, start, end, hasModifiers);
|
||||
},
|
||||
suggest: [
|
||||
{
|
||||
messageId: firstImportHasModifiers
|
||||
? "removeDuplicateImportWithModifiers"
|
||||
: "removeDuplicateImportWithoutModifiers",
|
||||
data: {
|
||||
modifiers: firstImportModifiers.join(" "),
|
||||
},
|
||||
fix(fixer) {
|
||||
const hasModifiers = (firstImportHasModifiers ||
|
||||
nodeHasModifiers) &&
|
||||
!hasSameModifiers;
|
||||
return getFixForImport(fixer, text, firstImportStart, firstImportEnd, hasModifiers);
|
||||
},
|
||||
},
|
||||
{
|
||||
messageId: nodeHasModifiers
|
||||
? "removeDuplicateImportWithModifiers"
|
||||
: "removeDuplicateImportWithoutModifiers",
|
||||
data: {
|
||||
modifiers: duplicateImportModifiers.join(" "),
|
||||
},
|
||||
fix(fixer) {
|
||||
const hasModifiers = (firstImportHasModifiers ||
|
||||
nodeHasModifiers) &&
|
||||
!hasSameModifiers;
|
||||
return getFixForImport(fixer, text, start, end, hasModifiers);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
else {
|
||||
imports.push(node);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user