Permalink
Cannot retrieve contributors at this time
250 lines (209 sloc)
9.79 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/operator-linebreak.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before | |
* @author Benoît Zugmeyer | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "layout", | |
docs: { | |
description: "Enforce consistent linebreak style for operators", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/operator-linebreak" | |
}, | |
schema: [ | |
{ | |
enum: ["after", "before", "none", null] | |
}, | |
{ | |
type: "object", | |
properties: { | |
overrides: { | |
type: "object", | |
additionalProperties: { | |
enum: ["after", "before", "none", "ignore"] | |
} | |
} | |
}, | |
additionalProperties: false | |
} | |
], | |
fixable: "code", | |
messages: { | |
operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.", | |
operatorAtEnd: "'{{operator}}' should be placed at the end of the line.", | |
badLinebreak: "Bad line breaking before and after '{{operator}}'.", | |
noLinebreak: "There should be no line break before or after '{{operator}}'." | |
} | |
}, | |
create(context) { | |
const usedDefaultGlobal = !context.options[0]; | |
const globalStyle = context.options[0] || "after"; | |
const options = context.options[1] || {}; | |
const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {}; | |
if (usedDefaultGlobal && !styleOverrides["?"]) { | |
styleOverrides["?"] = "before"; | |
} | |
if (usedDefaultGlobal && !styleOverrides[":"]) { | |
styleOverrides[":"] = "before"; | |
} | |
const sourceCode = context.sourceCode; | |
//-------------------------------------------------------------------------- | |
// Helpers | |
//-------------------------------------------------------------------------- | |
/** | |
* Gets a fixer function to fix rule issues | |
* @param {Token} operatorToken The operator token of an expression | |
* @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none' | |
* @returns {Function} A fixer function | |
*/ | |
function getFixer(operatorToken, desiredStyle) { | |
return fixer => { | |
const tokenBefore = sourceCode.getTokenBefore(operatorToken); | |
const tokenAfter = sourceCode.getTokenAfter(operatorToken); | |
const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]); | |
const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]); | |
const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken); | |
const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter); | |
let newTextBefore, newTextAfter; | |
if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") { | |
// If there is a comment before and after the operator, don't do a fix. | |
if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && | |
sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) { | |
return null; | |
} | |
/* | |
* If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator. | |
* foo && | |
* bar | |
* would get fixed to | |
* foo | |
* && bar | |
*/ | |
newTextBefore = textAfter; | |
newTextAfter = textBefore; | |
} else { | |
const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher(); | |
// Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings. | |
newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, ""); | |
newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, ""); | |
// If there was no change (due to interfering comments), don't output a fix. | |
if (newTextBefore === textBefore && newTextAfter === textAfter) { | |
return null; | |
} | |
} | |
if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) { | |
// To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-. | |
newTextAfter += " "; | |
} | |
return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter); | |
}; | |
} | |
/** | |
* Checks the operator placement | |
* @param {ASTNode} node The node to check | |
* @param {ASTNode} rightSide The node that comes after the operator in `node` | |
* @param {string} operator The operator | |
* @private | |
* @returns {void} | |
*/ | |
function validateNode(node, rightSide, operator) { | |
/* | |
* Find the operator token by searching from the right side, because between the left side and the operator | |
* there could be additional tokens from type annotations. Search specifically for the token which | |
* value equals the operator, in order to skip possible opening parentheses before the right side node. | |
*/ | |
const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator); | |
const leftToken = sourceCode.getTokenBefore(operatorToken); | |
const rightToken = sourceCode.getTokenAfter(operatorToken); | |
const operatorStyleOverride = styleOverrides[operator]; | |
const style = operatorStyleOverride || globalStyle; | |
const fix = getFixer(operatorToken, style); | |
// if single line | |
if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && | |
astUtils.isTokenOnSameLine(operatorToken, rightToken)) { | |
// do nothing. | |
} else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) && | |
!astUtils.isTokenOnSameLine(operatorToken, rightToken)) { | |
// lone operator | |
context.report({ | |
node, | |
loc: operatorToken.loc, | |
messageId: "badLinebreak", | |
data: { | |
operator | |
}, | |
fix | |
}); | |
} else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { | |
context.report({ | |
node, | |
loc: operatorToken.loc, | |
messageId: "operatorAtBeginning", | |
data: { | |
operator | |
}, | |
fix | |
}); | |
} else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { | |
context.report({ | |
node, | |
loc: operatorToken.loc, | |
messageId: "operatorAtEnd", | |
data: { | |
operator | |
}, | |
fix | |
}); | |
} else if (style === "none") { | |
context.report({ | |
node, | |
loc: operatorToken.loc, | |
messageId: "noLinebreak", | |
data: { | |
operator | |
}, | |
fix | |
}); | |
} | |
} | |
/** | |
* Validates a binary expression using `validateNode` | |
* @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated | |
* @returns {void} | |
*/ | |
function validateBinaryExpression(node) { | |
validateNode(node, node.right, node.operator); | |
} | |
//-------------------------------------------------------------------------- | |
// Public | |
//-------------------------------------------------------------------------- | |
return { | |
BinaryExpression: validateBinaryExpression, | |
LogicalExpression: validateBinaryExpression, | |
AssignmentExpression: validateBinaryExpression, | |
VariableDeclarator(node) { | |
if (node.init) { | |
validateNode(node, node.init, "="); | |
} | |
}, | |
PropertyDefinition(node) { | |
if (node.value) { | |
validateNode(node, node.value, "="); | |
} | |
}, | |
ConditionalExpression(node) { | |
validateNode(node, node.consequent, "?"); | |
validateNode(node, node.alternate, ":"); | |
} | |
}; | |
} | |
}; |