UNPKG

@stylistic/eslint-plugin-js

Version:

JavaScript stylistic rules for ESLint, migrated from [eslint core](https://github.com/eslint/eslint).

303 lines (300 loc) 9.07 kB
import { c as createRule, b as isCommaToken, h as getNextLocation } from '../utils.js'; import 'eslint-visitor-keys'; import 'espree'; const DEFAULT_OPTIONS = Object.freeze({ arrays: "never", objects: "never", imports: "never", exports: "never", functions: "never", importAttributes: "never", dynamicImports: "never" }); const closeBraces = ["}", "]", ")", ">"]; function isTrailingCommaAllowed(lastItem) { return lastItem.type !== "RestElement"; } function normalizeOptions(optionValue, ecmaVersion) { if (typeof optionValue === "string") { return { arrays: optionValue, objects: optionValue, imports: optionValue, exports: optionValue, functions: !ecmaVersion || ecmaVersion === "latest" ? optionValue : ecmaVersion < 2017 ? "ignore" : optionValue, importAttributes: optionValue, dynamicImports: !ecmaVersion || ecmaVersion === "latest" ? optionValue : ecmaVersion < 2025 ? "ignore" : optionValue }; } if (typeof optionValue === "object" && optionValue !== null) { return { arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, objects: optionValue.objects || DEFAULT_OPTIONS.objects, imports: optionValue.imports || DEFAULT_OPTIONS.imports, exports: optionValue.exports || DEFAULT_OPTIONS.exports, functions: optionValue.functions || DEFAULT_OPTIONS.functions, importAttributes: optionValue.importAttributes || DEFAULT_OPTIONS.importAttributes, dynamicImports: optionValue.dynamicImports || DEFAULT_OPTIONS.dynamicImports }; } return DEFAULT_OPTIONS; } var commaDangle = createRule({ name: "comma-dangle", package: "js", meta: { type: "layout", docs: { description: "Require or disallow trailing commas" }, fixable: "code", schema: { definitions: { value: { type: "string", enum: [ "always-multiline", "always", "never", "only-multiline" ] }, valueWithIgnore: { type: "string", enum: [ "always-multiline", "always", "ignore", "never", "only-multiline" ] } }, type: "array", items: [ { oneOf: [ { $ref: "#/definitions/value" }, { type: "object", properties: { arrays: { $ref: "#/definitions/valueWithIgnore" }, objects: { $ref: "#/definitions/valueWithIgnore" }, imports: { $ref: "#/definitions/valueWithIgnore" }, exports: { $ref: "#/definitions/valueWithIgnore" }, functions: { $ref: "#/definitions/valueWithIgnore" }, importAttributes: { $ref: "#/definitions/valueWithIgnore" }, dynamicImports: { $ref: "#/definitions/valueWithIgnore" } }, additionalProperties: false } ] } ], additionalItems: false }, messages: { unexpected: "Unexpected trailing comma.", missing: "Missing trailing comma." } }, create(context) { const ecmaVersion = context?.languageOptions?.ecmaVersion ?? context.parserOptions.ecmaVersion; const options = normalizeOptions(context.options[0], ecmaVersion); const sourceCode = context.sourceCode; function last(array) { if (!array) return null; return array[array.length - 1] ?? null; } function getTrailingToken(info) { switch (info.node.type) { case "ObjectExpression": case "ArrayExpression": case "CallExpression": case "NewExpression": case "ImportExpression": return sourceCode.getLastToken(info.node, 1); default: { const lastItem = info.lastItem; if (!lastItem) return null; const nextToken = sourceCode.getTokenAfter(lastItem); if (isCommaToken(nextToken)) return nextToken; return sourceCode.getLastToken(lastItem); } } } function isMultiline(info) { const lastItem = info.lastItem; if (!lastItem) return false; const penultimateToken = getTrailingToken(info); if (!penultimateToken) return false; const lastToken = sourceCode.getTokenAfter(penultimateToken); if (!lastToken) return false; return lastToken.loc.end.line !== penultimateToken.loc.end.line; } function forbidTrailingComma(info) { const lastItem = info.lastItem; if (!lastItem) return; const trailingToken = getTrailingToken(info); if (trailingToken && isCommaToken(trailingToken)) { context.report({ node: lastItem, loc: trailingToken.loc, messageId: "unexpected", *fix(fixer) { yield fixer.remove(trailingToken); yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), ""); yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); } }); } } function forceTrailingComma(info) { const lastItem = info.lastItem; if (!lastItem) return; if (!isTrailingCommaAllowed(lastItem)) { forbidTrailingComma(info); return; } const trailingToken = getTrailingToken(info); if (!trailingToken || trailingToken.value === ",") return; const nextToken = sourceCode.getTokenAfter(trailingToken); if (!nextToken || !closeBraces.includes(nextToken.value)) return; context.report({ node: lastItem, loc: { start: trailingToken.loc.end, end: getNextLocation(sourceCode, trailingToken.loc.end) }, messageId: "missing", *fix(fixer) { yield fixer.insertTextAfter(trailingToken, ","); yield fixer.insertTextBefore(trailingToken, ""); yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); } }); } function forceTrailingCommaIfMultiline(info) { if (isMultiline(info)) forceTrailingComma(info); else forbidTrailingComma(info); } function allowTrailingCommaIfMultiline(info) { if (!isMultiline(info)) forbidTrailingComma(info); } const predicate = { "always": forceTrailingComma, "always-multiline": forceTrailingCommaIfMultiline, "only-multiline": allowTrailingCommaIfMultiline, "never": forbidTrailingComma, ignore() { } }; return { ObjectExpression: (node) => { predicate[options.objects]({ node, lastItem: last(node.properties) }); }, ObjectPattern: (node) => { predicate[options.objects]({ node, lastItem: last(node.properties) }); }, ArrayExpression: (node) => { predicate[options.arrays]({ node, lastItem: last(node.elements) }); }, ArrayPattern: (node) => { predicate[options.arrays]({ node, lastItem: last(node.elements) }); }, ImportDeclaration: (node) => { const lastSpecifier = last(node.specifiers); if (lastSpecifier?.type === "ImportSpecifier") { predicate[options.imports]({ node, lastItem: lastSpecifier }); } predicate[options.importAttributes]({ node, lastItem: last(node.attributes) }); }, ExportNamedDeclaration: (node) => { predicate[options.exports]({ node, lastItem: last(node.specifiers) }); predicate[options.importAttributes]({ node, lastItem: last(node.attributes) }); }, ExportAllDeclaration: (node) => { predicate[options.importAttributes]({ node, lastItem: last(node.attributes) }); }, FunctionDeclaration: (node) => { predicate[options.functions]({ node, lastItem: last(node.params) }); }, FunctionExpression: (node) => { predicate[options.functions]({ node, lastItem: last(node.params) }); }, ArrowFunctionExpression: (node) => { predicate[options.functions]({ node, lastItem: last(node.params) }); }, CallExpression: (node) => { predicate[options.functions]({ node, lastItem: last(node.arguments) }); }, NewExpression: (node) => { predicate[options.functions]({ node, lastItem: last(node.arguments) }); }, ImportExpression: (node) => { predicate[options.dynamicImports]({ node, lastItem: node.options ?? node.source }); } }; } }); export { commaDangle as default };