Spaces:
Runtime error
Runtime error
/** | |
* @fileoverview Comma style - enforces comma styles of two types: last and first | |
* @author Vignesh Anand aka vegetableman | |
*/ | |
; | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
module.exports = { | |
meta: { | |
type: "layout", | |
docs: { | |
description: "enforce consistent comma style", | |
category: "Stylistic Issues", | |
recommended: false, | |
url: "https://eslint.org/docs/rules/comma-style" | |
}, | |
fixable: "code", | |
schema: [ | |
{ | |
enum: ["first", "last"] | |
}, | |
{ | |
type: "object", | |
properties: { | |
exceptions: { | |
type: "object", | |
additionalProperties: { | |
type: "boolean" | |
} | |
} | |
}, | |
additionalProperties: false | |
} | |
], | |
messages: { | |
unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.", | |
expectedCommaFirst: "',' should be placed first.", | |
expectedCommaLast: "',' should be placed last." | |
} | |
}, | |
create(context) { | |
const style = context.options[0] || "last", | |
sourceCode = context.getSourceCode(); | |
const exceptions = { | |
ArrayPattern: true, | |
ArrowFunctionExpression: true, | |
CallExpression: true, | |
FunctionDeclaration: true, | |
FunctionExpression: true, | |
ImportDeclaration: true, | |
ObjectPattern: true, | |
NewExpression: true | |
}; | |
if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) { | |
const keys = Object.keys(context.options[1].exceptions); | |
for (let i = 0; i < keys.length; i++) { | |
exceptions[keys[i]] = context.options[1].exceptions[keys[i]]; | |
} | |
} | |
//-------------------------------------------------------------------------- | |
// Helpers | |
//-------------------------------------------------------------------------- | |
/** | |
* Modified text based on the style | |
* @param {string} styleType Style type | |
* @param {string} text Source code text | |
* @returns {string} modified text | |
* @private | |
*/ | |
function getReplacedText(styleType, text) { | |
switch (styleType) { | |
case "between": | |
return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`; | |
case "first": | |
return `${text},`; | |
case "last": | |
return `,${text}`; | |
default: | |
return ""; | |
} | |
} | |
/** | |
* Determines the fixer function for a given style. | |
* @param {string} styleType comma style | |
* @param {ASTNode} previousItemToken The token to check. | |
* @param {ASTNode} commaToken The token to check. | |
* @param {ASTNode} currentItemToken The token to check. | |
* @returns {Function} Fixer function | |
* @private | |
*/ | |
function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) { | |
const text = | |
sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) + | |
sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]); | |
const range = [previousItemToken.range[1], currentItemToken.range[0]]; | |
return function(fixer) { | |
return fixer.replaceTextRange(range, getReplacedText(styleType, text)); | |
}; | |
} | |
/** | |
* Validates the spacing around single items in lists. | |
* @param {Token} previousItemToken The last token from the previous item. | |
* @param {Token} commaToken The token representing the comma. | |
* @param {Token} currentItemToken The first token of the current item. | |
* @param {Token} reportItem The item to use when reporting an error. | |
* @returns {void} | |
* @private | |
*/ | |
function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) { | |
// if single line | |
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && | |
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { | |
// do nothing. | |
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && | |
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { | |
const comment = sourceCode.getCommentsAfter(commaToken)[0]; | |
const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment) | |
? style | |
: "between"; | |
// lone comma | |
context.report({ | |
node: reportItem, | |
loc: commaToken.loc, | |
messageId: "unexpectedLineBeforeAndAfterComma", | |
fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) | |
}); | |
} else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { | |
context.report({ | |
node: reportItem, | |
loc: commaToken.loc, | |
messageId: "expectedCommaFirst", | |
fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) | |
}); | |
} else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { | |
context.report({ | |
node: reportItem, | |
loc: commaToken.loc, | |
messageId: "expectedCommaLast", | |
fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) | |
}); | |
} | |
} | |
/** | |
* Checks the comma placement with regards to a declaration/property/element | |
* @param {ASTNode} node The binary expression node to check | |
* @param {string} property The property of the node containing child nodes. | |
* @private | |
* @returns {void} | |
*/ | |
function validateComma(node, property) { | |
const items = node[property], | |
arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern"); | |
if (items.length > 1 || arrayLiteral) { | |
// seed as opening [ | |
let previousItemToken = sourceCode.getFirstToken(node); | |
items.forEach(item => { | |
const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken, | |
currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken), | |
reportItem = item || currentItemToken; | |
/* | |
* This works by comparing three token locations: | |
* - previousItemToken is the last token of the previous item | |
* - commaToken is the location of the comma before the current item | |
* - currentItemToken is the first token of the current item | |
* | |
* These values get switched around if item is undefined. | |
* previousItemToken will refer to the last token not belonging | |
* to the current item, which could be a comma or an opening | |
* square bracket. currentItemToken could be a comma. | |
* | |
* All comparisons are done based on these tokens directly, so | |
* they are always valid regardless of an undefined item. | |
*/ | |
if (astUtils.isCommaToken(commaToken)) { | |
validateCommaItemSpacing(previousItemToken, commaToken, | |
currentItemToken, reportItem); | |
} | |
if (item) { | |
const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken); | |
previousItemToken = tokenAfterItem | |
? sourceCode.getTokenBefore(tokenAfterItem) | |
: sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]; | |
} | |
}); | |
/* | |
* Special case for array literals that have empty last items, such | |
* as [ 1, 2, ]. These arrays only have two items show up in the | |
* AST, so we need to look at the token to verify that there's no | |
* dangling comma. | |
*/ | |
if (arrayLiteral) { | |
const lastToken = sourceCode.getLastToken(node), | |
nextToLastToken = sourceCode.getTokenBefore(lastToken); | |
if (astUtils.isCommaToken(nextToLastToken)) { | |
validateCommaItemSpacing( | |
sourceCode.getTokenBefore(nextToLastToken), | |
nextToLastToken, | |
lastToken, | |
lastToken | |
); | |
} | |
} | |
} | |
} | |
//-------------------------------------------------------------------------- | |
// Public | |
//-------------------------------------------------------------------------- | |
const nodes = {}; | |
if (!exceptions.VariableDeclaration) { | |
nodes.VariableDeclaration = function(node) { | |
validateComma(node, "declarations"); | |
}; | |
} | |
if (!exceptions.ObjectExpression) { | |
nodes.ObjectExpression = function(node) { | |
validateComma(node, "properties"); | |
}; | |
} | |
if (!exceptions.ObjectPattern) { | |
nodes.ObjectPattern = function(node) { | |
validateComma(node, "properties"); | |
}; | |
} | |
if (!exceptions.ArrayExpression) { | |
nodes.ArrayExpression = function(node) { | |
validateComma(node, "elements"); | |
}; | |
} | |
if (!exceptions.ArrayPattern) { | |
nodes.ArrayPattern = function(node) { | |
validateComma(node, "elements"); | |
}; | |
} | |
if (!exceptions.FunctionDeclaration) { | |
nodes.FunctionDeclaration = function(node) { | |
validateComma(node, "params"); | |
}; | |
} | |
if (!exceptions.FunctionExpression) { | |
nodes.FunctionExpression = function(node) { | |
validateComma(node, "params"); | |
}; | |
} | |
if (!exceptions.ArrowFunctionExpression) { | |
nodes.ArrowFunctionExpression = function(node) { | |
validateComma(node, "params"); | |
}; | |
} | |
if (!exceptions.CallExpression) { | |
nodes.CallExpression = function(node) { | |
validateComma(node, "arguments"); | |
}; | |
} | |
if (!exceptions.ImportDeclaration) { | |
nodes.ImportDeclaration = function(node) { | |
validateComma(node, "specifiers"); | |
}; | |
} | |
if (!exceptions.NewExpression) { | |
nodes.NewExpression = function(node) { | |
validateComma(node, "arguments"); | |
}; | |
} | |
return nodes; | |
} | |
}; | |