mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
7.9 KiB
223 lines
7.9 KiB
/**
|
|
* @fileoverview Rule to forbid or enforce dangling commas.
|
|
* @author Ian Christian Myers
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
var lodash = require("lodash");
|
|
|
|
/**
|
|
* Checks whether or not a trailing comma is allowed in a given node.
|
|
* `ArrayPattern` which has `RestElement` disallows it.
|
|
*
|
|
* @param {ASTNode} node - A node to check.
|
|
* @param {ASTNode} lastItem - The node of the last element in the given node.
|
|
* @returns {boolean} `true` if a trailing comma is allowed.
|
|
*/
|
|
function isTrailingCommaAllowed(node, lastItem) {
|
|
return node.type !== "ArrayPattern" || lastItem.type !== "RestElement";
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
module.exports = {
|
|
meta: {
|
|
docs: {
|
|
description: "require or disallow trailing commas",
|
|
category: "Possible Errors",
|
|
recommended: true
|
|
},
|
|
|
|
fixable: "code",
|
|
|
|
schema: [
|
|
{
|
|
enum: ["always", "always-multiline", "only-multiline", "never"]
|
|
}
|
|
]
|
|
},
|
|
|
|
create: function(context) {
|
|
var mode = context.options[0];
|
|
var UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
|
|
var MISSING_MESSAGE = "Missing trailing comma.";
|
|
|
|
/**
|
|
* Checks whether or not a given node is multiline.
|
|
* This rule handles a given node as multiline when the closing parenthesis
|
|
* and the last element are not on the same line.
|
|
*
|
|
* @param {ASTNode} node - A node to check.
|
|
* @returns {boolean} `true` if the node is multiline.
|
|
*/
|
|
function isMultiline(node) {
|
|
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
|
|
|
if (!lastItem) {
|
|
return false;
|
|
}
|
|
|
|
var sourceCode = context.getSourceCode(),
|
|
penultimateToken = sourceCode.getLastToken(lastItem),
|
|
lastToken = sourceCode.getTokenAfter(penultimateToken);
|
|
|
|
// parentheses are a pain
|
|
while (lastToken.value === ")") {
|
|
penultimateToken = lastToken;
|
|
lastToken = sourceCode.getTokenAfter(lastToken);
|
|
}
|
|
|
|
if (lastToken.value === ",") {
|
|
penultimateToken = lastToken;
|
|
lastToken = sourceCode.getTokenAfter(lastToken);
|
|
}
|
|
|
|
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
|
|
}
|
|
|
|
/**
|
|
* Reports a trailing comma if it exists.
|
|
*
|
|
* @param {ASTNode} node - A node to check. Its type is one of
|
|
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
|
* ImportDeclaration, and ExportNamedDeclaration.
|
|
* @returns {void}
|
|
*/
|
|
function forbidTrailingComma(node) {
|
|
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
|
|
|
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
|
return;
|
|
}
|
|
|
|
var sourceCode = context.getSourceCode(),
|
|
trailingToken;
|
|
|
|
// last item can be surrounded by parentheses for object and array literals
|
|
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
|
|
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
|
|
} else {
|
|
trailingToken = sourceCode.getTokenAfter(lastItem);
|
|
}
|
|
|
|
if (trailingToken.value === ",") {
|
|
context.report({
|
|
node: lastItem,
|
|
loc: trailingToken.loc.start,
|
|
message: UNEXPECTED_MESSAGE,
|
|
fix: function(fixer) {
|
|
return fixer.remove(trailingToken);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reports the last element of a given node if it does not have a trailing
|
|
* comma.
|
|
*
|
|
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
|
|
* comma is disallowed, so report if it exists.
|
|
*
|
|
* @param {ASTNode} node - A node to check. Its type is one of
|
|
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
|
* ImportDeclaration, and ExportNamedDeclaration.
|
|
* @returns {void}
|
|
*/
|
|
function forceTrailingComma(node) {
|
|
var lastItem = lodash.last(node.properties || node.elements || node.specifiers);
|
|
|
|
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
|
return;
|
|
}
|
|
if (!isTrailingCommaAllowed(node, lastItem)) {
|
|
forbidTrailingComma(node);
|
|
return;
|
|
}
|
|
|
|
var sourceCode = context.getSourceCode(),
|
|
trailingToken;
|
|
|
|
// last item can be surrounded by parentheses for object and array literals
|
|
if (node.type === "ObjectExpression" || node.type === "ArrayExpression") {
|
|
trailingToken = sourceCode.getTokenBefore(sourceCode.getLastToken(node));
|
|
} else {
|
|
trailingToken = sourceCode.getTokenAfter(lastItem);
|
|
}
|
|
|
|
if (trailingToken.value !== ",") {
|
|
context.report({
|
|
node: lastItem,
|
|
loc: lastItem.loc.end,
|
|
message: MISSING_MESSAGE,
|
|
fix: function(fixer) {
|
|
return fixer.insertTextAfter(lastItem, ",");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a given node is multiline, reports the last element of a given node
|
|
* when it does not have a trailing comma.
|
|
* Otherwise, reports a trailing comma if it exists.
|
|
*
|
|
* @param {ASTNode} node - A node to check. Its type is one of
|
|
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
|
* ImportDeclaration, and ExportNamedDeclaration.
|
|
* @returns {void}
|
|
*/
|
|
function forceTrailingCommaIfMultiline(node) {
|
|
if (isMultiline(node)) {
|
|
forceTrailingComma(node);
|
|
} else {
|
|
forbidTrailingComma(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Only if a given node is not multiline, reports the last element of a given node
|
|
* when it does not have a trailing comma.
|
|
* Otherwise, reports a trailing comma if it exists.
|
|
*
|
|
* @param {ASTNode} node - A node to check. Its type is one of
|
|
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
|
* ImportDeclaration, and ExportNamedDeclaration.
|
|
* @returns {void}
|
|
*/
|
|
function allowTrailingCommaIfMultiline(node) {
|
|
if (!isMultiline(node)) {
|
|
forbidTrailingComma(node);
|
|
}
|
|
}
|
|
|
|
// Chooses a checking function.
|
|
var checkForTrailingComma;
|
|
|
|
if (mode === "always") {
|
|
checkForTrailingComma = forceTrailingComma;
|
|
} else if (mode === "always-multiline") {
|
|
checkForTrailingComma = forceTrailingCommaIfMultiline;
|
|
} else if (mode === "only-multiline") {
|
|
checkForTrailingComma = allowTrailingCommaIfMultiline;
|
|
} else {
|
|
checkForTrailingComma = forbidTrailingComma;
|
|
}
|
|
|
|
return {
|
|
ObjectExpression: checkForTrailingComma,
|
|
ObjectPattern: checkForTrailingComma,
|
|
ArrayExpression: checkForTrailingComma,
|
|
ArrayPattern: checkForTrailingComma,
|
|
ImportDeclaration: checkForTrailingComma,
|
|
ExportNamedDeclaration: checkForTrailingComma
|
|
};
|
|
}
|
|
};
|
|
|