@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
JavaScript
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 };