|
|
|
/**
|
|
|
|
* @fileoverview Rule to forbid or enforce dangling commas.
|
|
|
|
* @author Ian Christian Myers
|
|
|
|
* @copyright 2015 Toru Nagashima
|
|
|
|
* @copyright 2015 Mathias Schreck
|
|
|
|
* @copyright 2013 Ian Christian Myers
|
|
|
|
* See LICENSE file in root directory for full license.
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Helpers
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the last element of a given array.
|
|
|
|
*
|
|
|
|
* @param {*[]} xs - An array to get.
|
|
|
|
* @returns {*} The last element, or undefined.
|
|
|
|
*/
|
|
|
|
function getLast(xs) {
|
|
|
|
if (xs.length === 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return xs[xs.length - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
switch (node.type) {
|
|
|
|
case "ArrayPattern":
|
|
|
|
// TODO(t-nagashima): Remove SpreadElement after https://github.com/eslint/espree/issues/194 was fixed.
|
|
|
|
return (
|
|
|
|
lastItem.type !== "RestElement" &&
|
|
|
|
lastItem.type !== "SpreadElement"
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODO(t-nagashima): Remove this case after https://github.com/eslint/espree/issues/195 was fixed.
|
|
|
|
case "ArrayExpression":
|
|
|
|
return (
|
|
|
|
node.parent.type !== "ForOfStatement" ||
|
|
|
|
node.parent.left !== node ||
|
|
|
|
lastItem.type !== "SpreadElement"
|
|
|
|
);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Rule Definition
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
module.exports = 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 = getLast(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 = getLast(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(
|
|
|
|
lastItem,
|
|
|
|
trailingToken.loc.start,
|
|
|
|
UNEXPECTED_MESSAGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 = getLast(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(
|
|
|
|
lastItem,
|
|
|
|
lastItem.loc.end,
|
|
|
|
MISSING_MESSAGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.schema = [
|
|
|
|
{
|
|
|
|
"enum": ["always", "always-multiline", "only-multiline", "never"]
|
|
|
|
}
|
|
|
|
];
|