mirror of https://github.com/lukechilds/node.git
Browse Source
Update ESLint to 2.1.0. ESLint has a number of potentially-useful new features but this change attempts to be minimal in its changes. However, some things could not be avoided reasonably. ESLint 2.1.0 found a few lint issues that ESLing 1.x missed with template strings that did not take advantage of any features of template strings, and `let` declarations where `const` sufficed. Additionally, ESLint 2.1.0 removes some granularity around enabling ES6 features. Some features (e.g., spread operator) that had been turned off in our configuration for ESLint 1.x are now permitted. PR-URL: https://github.com/nodejs/node/pull/5214 Reviewed-By: Michaël Zasso <mic.besace@gmail.com> Reviewed-By: jbergstroem - Johan Bergström <bugs@bergstroem.nu> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: Myles Borins <myles.borins@gmail.com>process-exit-stdio-flushing
Rich Trott
9 years ago
2898 changed files with 115478 additions and 128590 deletions
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* @fileoverview Default CLIEngineOptions. |
||||
|
* @author Ian VanSchooten |
||||
|
* @copyright 2016 Ian VanSchooten. All rights reserved. |
||||
|
* See LICENSE in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
var DEFAULT_PARSER = require("../conf/eslint.json").parser; |
||||
|
|
||||
|
module.exports = { |
||||
|
configFile: null, |
||||
|
baseConfig: false, |
||||
|
rulePaths: [], |
||||
|
useEslintrc: true, |
||||
|
envs: [], |
||||
|
globals: [], |
||||
|
rules: {}, |
||||
|
extensions: [".js"], |
||||
|
ignore: true, |
||||
|
ignorePath: null, |
||||
|
parser: DEFAULT_PARSER, |
||||
|
cache: false, |
||||
|
// in order to honor the cacheFile option if specified
|
||||
|
// this option should not have a default value otherwise
|
||||
|
// it will always be used
|
||||
|
cacheLocation: "", |
||||
|
cacheFile: ".eslintcache", |
||||
|
fix: false, |
||||
|
allowInlineConfig: true, |
||||
|
cwd: process.cwd() |
||||
|
}; |
@ -0,0 +1,640 @@ |
|||||
|
/** |
||||
|
* @fileoverview A class of the code path analyzer. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var assert = require("assert"), |
||||
|
CodePath = require("./code-path"), |
||||
|
CodePathSegment = require("./code-path-segment"), |
||||
|
IdGenerator = require("./id-generator"), |
||||
|
debug = require("./debug-helpers"), |
||||
|
astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given node is a `case` node (not `default` node). |
||||
|
* |
||||
|
* @param {ASTNode} node - A `SwitchCase` node to check. |
||||
|
* @returns {boolean} `true` if the node is a `case` node (not `default` node). |
||||
|
*/ |
||||
|
function isCaseNode(node) { |
||||
|
return Boolean(node.test); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given logical expression node goes different path |
||||
|
* between the `true` case and the `false` case. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @returns {boolean} `true` if the node is a test of a choice statement. |
||||
|
*/ |
||||
|
function isForkingByTrueOrFalse(node) { |
||||
|
var parent = node.parent; |
||||
|
switch (parent.type) { |
||||
|
case "ConditionalExpression": |
||||
|
case "IfStatement": |
||||
|
case "WhileStatement": |
||||
|
case "DoWhileStatement": |
||||
|
case "ForStatement": |
||||
|
return parent.test === node; |
||||
|
|
||||
|
case "LogicalExpression": |
||||
|
return true; |
||||
|
|
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the boolean value of a given literal node. |
||||
|
* |
||||
|
* This is used to detect infinity loops (e.g. `while (true) {}`). |
||||
|
* Statements preceded by an infinity loop are unreachable if the loop didn't |
||||
|
* have any `break` statement. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to get. |
||||
|
* @returns {boolean|undefined} a boolean value if the node is a Literal node, |
||||
|
* otherwise `undefined`. |
||||
|
*/ |
||||
|
function getBooleanValueIfSimpleConstant(node) { |
||||
|
if (node.type === "Literal") { |
||||
|
return Boolean(node.value); |
||||
|
} |
||||
|
return void 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks that a given identifier node is a reference or not. |
||||
|
* |
||||
|
* This is used to detect the first throwable node in a `try` block. |
||||
|
* |
||||
|
* @param {ASTNode} node - An Identifier node to check. |
||||
|
* @returns {boolean} `true` if the node is a reference. |
||||
|
*/ |
||||
|
function isIdentifierReference(node) { |
||||
|
var parent = node.parent; |
||||
|
|
||||
|
switch (parent.type) { |
||||
|
case "LabeledStatement": |
||||
|
case "BreakStatement": |
||||
|
case "ContinueStatement": |
||||
|
case "ArrayPattern": |
||||
|
case "RestElement": |
||||
|
case "ImportSpecifier": |
||||
|
case "ImportDefaultSpecifier": |
||||
|
case "ImportNamespaceSpecifier": |
||||
|
case "CatchClause": |
||||
|
return false; |
||||
|
|
||||
|
case "FunctionDeclaration": |
||||
|
case "FunctionExpression": |
||||
|
case "ArrowFunctionExpression": |
||||
|
case "ClassDeclaration": |
||||
|
case "ClassExpression": |
||||
|
case "VariableDeclarator": |
||||
|
return parent.id !== node; |
||||
|
|
||||
|
case "Property": |
||||
|
case "MethodDefinition": |
||||
|
return ( |
||||
|
parent.key !== node || |
||||
|
parent.computed || |
||||
|
parent.shorthand |
||||
|
); |
||||
|
|
||||
|
case "AssignmentPattern": |
||||
|
return parent.key !== node; |
||||
|
|
||||
|
default: |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the current segment with the head segment. |
||||
|
* This is similar to local branches and tracking branches of git. |
||||
|
* |
||||
|
* To separate the current and the head is in order to not make useless segments. |
||||
|
* |
||||
|
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" events |
||||
|
* are fired. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function forwardCurrentToHead(analyzer, node) { |
||||
|
var codePath = analyzer.codePath; |
||||
|
var state = CodePath.getState(codePath); |
||||
|
var currentSegments = state.currentSegments; |
||||
|
var headSegments = state.headSegments; |
||||
|
var end = Math.max(currentSegments.length, headSegments.length); |
||||
|
var i, currentSegment, headSegment; |
||||
|
|
||||
|
// Fires leaving events.
|
||||
|
for (i = 0; i < end; ++i) { |
||||
|
currentSegment = currentSegments[i]; |
||||
|
headSegment = headSegments[i]; |
||||
|
|
||||
|
if (currentSegment !== headSegment && currentSegment) { |
||||
|
debug.dump("onCodePathSegmentEnd " + currentSegment.id); |
||||
|
|
||||
|
if (currentSegment.reachable) { |
||||
|
analyzer.emitter.emit( |
||||
|
"onCodePathSegmentEnd", |
||||
|
currentSegment, |
||||
|
node); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Update state.
|
||||
|
state.currentSegments = headSegments; |
||||
|
|
||||
|
// Fires entering events.
|
||||
|
for (i = 0; i < end; ++i) { |
||||
|
currentSegment = currentSegments[i]; |
||||
|
headSegment = headSegments[i]; |
||||
|
|
||||
|
if (currentSegment !== headSegment && headSegment) { |
||||
|
debug.dump("onCodePathSegmentStart " + headSegment.id); |
||||
|
|
||||
|
CodePathSegment.markUsed(headSegment); |
||||
|
if (headSegment.reachable) { |
||||
|
analyzer.emitter.emit( |
||||
|
"onCodePathSegmentStart", |
||||
|
headSegment, |
||||
|
node); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the current segment with empty. |
||||
|
* This is called at the last of functions or the program. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function leaveFromCurrentSegment(analyzer, node) { |
||||
|
var state = CodePath.getState(analyzer.codePath); |
||||
|
var currentSegments = state.currentSegments; |
||||
|
|
||||
|
for (var i = 0; i < currentSegments.length; ++i) { |
||||
|
var currentSegment = currentSegments[i]; |
||||
|
|
||||
|
debug.dump("onCodePathSegmentEnd " + currentSegment.id); |
||||
|
if (currentSegment.reachable) { |
||||
|
analyzer.emitter.emit( |
||||
|
"onCodePathSegmentEnd", |
||||
|
currentSegment, |
||||
|
node); |
||||
|
} |
||||
|
} |
||||
|
state.currentSegments = []; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the code path due to the position of a given node in the parent node |
||||
|
* thereof. |
||||
|
* |
||||
|
* For example, if the node is `parent.consequent`, this creates a fork from the |
||||
|
* current path. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function preprocess(analyzer, node) { |
||||
|
var codePath = analyzer.codePath; |
||||
|
var state = CodePath.getState(codePath); |
||||
|
var parent = node.parent; |
||||
|
|
||||
|
switch (parent.type) { |
||||
|
case "LogicalExpression": |
||||
|
if (parent.right === node) { |
||||
|
state.makeLogicalRight(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "ConditionalExpression": |
||||
|
case "IfStatement": |
||||
|
// Fork if this node is at `consequent`/`alternate`.
|
||||
|
// `popForkContext()` exists at `IfStatement:exit` and
|
||||
|
// `ConditionalExpression:exit`.
|
||||
|
if (parent.consequent === node) { |
||||
|
state.makeIfConsequent(); |
||||
|
} else if (parent.alternate === node) { |
||||
|
state.makeIfAlternate(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "SwitchCase": |
||||
|
if (parent.consequent[0] === node) { |
||||
|
state.makeSwitchCaseBody(false, !parent.test); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "TryStatement": |
||||
|
if (parent.handler === node) { |
||||
|
state.makeCatchBlock(); |
||||
|
} else if (parent.finalizer === node) { |
||||
|
state.makeFinallyBlock(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "WhileStatement": |
||||
|
if (parent.test === node) { |
||||
|
state.makeWhileTest(getBooleanValueIfSimpleConstant(node)); |
||||
|
} else { |
||||
|
assert(parent.body === node); |
||||
|
state.makeWhileBody(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "DoWhileStatement": |
||||
|
if (parent.body === node) { |
||||
|
state.makeDoWhileBody(); |
||||
|
} else { |
||||
|
assert(parent.test === node); |
||||
|
state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node)); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "ForStatement": |
||||
|
if (parent.test === node) { |
||||
|
state.makeForTest(getBooleanValueIfSimpleConstant(node)); |
||||
|
} else if (parent.update === node) { |
||||
|
state.makeForUpdate(); |
||||
|
} else if (parent.body === node) { |
||||
|
state.makeForBody(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "ForInStatement": |
||||
|
case "ForOfStatement": |
||||
|
if (parent.left === node) { |
||||
|
state.makeForInOfLeft(); |
||||
|
} else if (parent.right === node) { |
||||
|
state.makeForInOfRight(); |
||||
|
} else { |
||||
|
assert(parent.body === node); |
||||
|
state.makeForInOfBody(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "AssignmentPattern": |
||||
|
// Fork if this node is at `right`.
|
||||
|
// `left` is executed always, so it uses the current path.
|
||||
|
// `popForkContext()` exists at `AssignmentPattern:exit`.
|
||||
|
if (parent.right === node) { |
||||
|
state.pushForkContext(); |
||||
|
state.forkBypassPath(); |
||||
|
state.forkPath(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the code path due to the type of a given node in entering. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function processCodePathToEnter(analyzer, node) { |
||||
|
var codePath = analyzer.codePath; |
||||
|
var state = codePath && CodePath.getState(codePath); |
||||
|
var parent = node.parent; |
||||
|
|
||||
|
switch (node.type) { |
||||
|
case "Program": |
||||
|
case "FunctionDeclaration": |
||||
|
case "FunctionExpression": |
||||
|
case "ArrowFunctionExpression": |
||||
|
if (codePath) { |
||||
|
// Emits onCodePathSegmentStart events if updated.
|
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
debug.dumpState(node, state, false); |
||||
|
} |
||||
|
|
||||
|
// Create the code path of this scope.
|
||||
|
codePath = analyzer.codePath = new CodePath( |
||||
|
analyzer.idGenerator.next(), |
||||
|
codePath, |
||||
|
analyzer.onLooped |
||||
|
); |
||||
|
state = CodePath.getState(codePath); |
||||
|
|
||||
|
// Emits onCodePathStart events.
|
||||
|
debug.dump("onCodePathStart " + codePath.id); |
||||
|
analyzer.emitter.emit("onCodePathStart", codePath, node); |
||||
|
break; |
||||
|
|
||||
|
case "LogicalExpression": |
||||
|
state.pushChoiceContext(node.operator, isForkingByTrueOrFalse(node)); |
||||
|
break; |
||||
|
|
||||
|
case "ConditionalExpression": |
||||
|
case "IfStatement": |
||||
|
state.pushChoiceContext("test", false); |
||||
|
break; |
||||
|
|
||||
|
case "SwitchStatement": |
||||
|
state.pushSwitchContext( |
||||
|
node.cases.some(isCaseNode), |
||||
|
astUtils.getLabel(node)); |
||||
|
break; |
||||
|
|
||||
|
case "TryStatement": |
||||
|
state.pushTryContext(Boolean(node.finalizer)); |
||||
|
break; |
||||
|
|
||||
|
case "SwitchCase": |
||||
|
// Fork if this node is after the 2st node in `cases`.
|
||||
|
// It's similar to `else` blocks.
|
||||
|
// The next `test` node is processed in this path.
|
||||
|
if (parent.discriminant !== node && parent.cases[0] !== node) { |
||||
|
state.forkPath(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "WhileStatement": |
||||
|
case "DoWhileStatement": |
||||
|
case "ForStatement": |
||||
|
case "ForInStatement": |
||||
|
case "ForOfStatement": |
||||
|
state.pushLoopContext(node.type, astUtils.getLabel(node)); |
||||
|
break; |
||||
|
|
||||
|
case "LabeledStatement": |
||||
|
if (!astUtils.isBreakableStatement(node.body)) { |
||||
|
state.pushBreakContext(false, node.label.name); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Emits onCodePathSegmentStart events if updated.
|
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
debug.dumpState(node, state, false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the code path due to the type of a given node in leaving. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function processCodePathToExit(analyzer, node) { |
||||
|
var codePath = analyzer.codePath; |
||||
|
var state = CodePath.getState(codePath); |
||||
|
var dontForward = false; |
||||
|
|
||||
|
switch (node.type) { |
||||
|
case "IfStatement": |
||||
|
case "ConditionalExpression": |
||||
|
case "LogicalExpression": |
||||
|
state.popChoiceContext(); |
||||
|
break; |
||||
|
|
||||
|
case "SwitchStatement": |
||||
|
state.popSwitchContext(); |
||||
|
break; |
||||
|
|
||||
|
case "SwitchCase": |
||||
|
// This is the same as the process at the 1st `consequent` node in
|
||||
|
// `preprocess` function.
|
||||
|
// Must do if this `consequent` is empty.
|
||||
|
if (node.consequent.length === 0) { |
||||
|
state.makeSwitchCaseBody(true, !node.test); |
||||
|
} |
||||
|
if (state.forkContext.reachable) { |
||||
|
dontForward = true; |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "TryStatement": |
||||
|
state.popTryContext(); |
||||
|
break; |
||||
|
|
||||
|
case "BreakStatement": |
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
state.makeBreak(node.label && node.label.name); |
||||
|
dontForward = true; |
||||
|
break; |
||||
|
|
||||
|
case "ContinueStatement": |
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
state.makeContinue(node.label && node.label.name); |
||||
|
dontForward = true; |
||||
|
break; |
||||
|
|
||||
|
case "ReturnStatement": |
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
state.makeReturn(); |
||||
|
dontForward = true; |
||||
|
break; |
||||
|
|
||||
|
case "ThrowStatement": |
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
state.makeThrow(); |
||||
|
dontForward = true; |
||||
|
break; |
||||
|
|
||||
|
case "Identifier": |
||||
|
if (isIdentifierReference(node)) { |
||||
|
state.makeFirstThrowablePathInTryBlock(); |
||||
|
dontForward = true; |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
case "CallExpression": |
||||
|
case "MemberExpression": |
||||
|
case "NewExpression": |
||||
|
state.makeFirstThrowablePathInTryBlock(); |
||||
|
break; |
||||
|
|
||||
|
case "WhileStatement": |
||||
|
case "DoWhileStatement": |
||||
|
case "ForStatement": |
||||
|
case "ForInStatement": |
||||
|
case "ForOfStatement": |
||||
|
state.popLoopContext(); |
||||
|
break; |
||||
|
|
||||
|
case "AssignmentPattern": |
||||
|
state.popForkContext(); |
||||
|
break; |
||||
|
|
||||
|
case "LabeledStatement": |
||||
|
if (!astUtils.isBreakableStatement(node.body)) { |
||||
|
state.popBreakContext(); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Skip updating the current segment to avoid creating useless segments if
|
||||
|
// the node type is the same as the parent node type.
|
||||
|
if (!dontForward && (!node.parent || node.type !== node.parent.type)) { |
||||
|
// Emits onCodePathSegmentStart events if updated.
|
||||
|
forwardCurrentToHead(analyzer, node); |
||||
|
} |
||||
|
debug.dumpState(node, state, true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Updates the code path to finalize the current code path. |
||||
|
* |
||||
|
* @param {CodePathAnalyzer} analyzer - The instance. |
||||
|
* @param {ASTNode} node - The current AST node. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function postprocess(analyzer, node) { |
||||
|
switch (node.type) { |
||||
|
case "Program": |
||||
|
case "FunctionDeclaration": |
||||
|
case "FunctionExpression": |
||||
|
case "ArrowFunctionExpression": |
||||
|
var codePath = analyzer.codePath; |
||||
|
|
||||
|
// Mark the current path as the final node.
|
||||
|
CodePath.getState(codePath).makeFinal(); |
||||
|
|
||||
|
// Emits onCodePathSegmentEnd event of the current segments.
|
||||
|
leaveFromCurrentSegment(analyzer, node); |
||||
|
|
||||
|
// Emits onCodePathEnd event of this code path.
|
||||
|
debug.dump("onCodePathEnd " + codePath.id); |
||||
|
analyzer.emitter.emit("onCodePathEnd", codePath, node); |
||||
|
debug.dumpDot(codePath); |
||||
|
|
||||
|
codePath = analyzer.codePath = analyzer.codePath.upper; |
||||
|
if (codePath) { |
||||
|
debug.dumpState(node, CodePath.getState(codePath), true); |
||||
|
} |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* The class to analyze code paths. |
||||
|
* This class implements the EventGenerator interface. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @param {EventGenerator} eventGenerator - An event generator to wrap. |
||||
|
*/ |
||||
|
function CodePathAnalyzer(eventGenerator) { |
||||
|
this.original = eventGenerator; |
||||
|
this.emitter = eventGenerator.emitter; |
||||
|
this.codePath = null; |
||||
|
this.idGenerator = new IdGenerator("s"); |
||||
|
this.currentNode = null; |
||||
|
this.onLooped = this.onLooped.bind(this); |
||||
|
} |
||||
|
|
||||
|
CodePathAnalyzer.prototype = { |
||||
|
constructor: CodePathAnalyzer, |
||||
|
|
||||
|
/** |
||||
|
* Does the process to enter a given AST node. |
||||
|
* This updates state of analysis and calls `enterNode` of the wrapped. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node which is entering. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
enterNode: function(node) { |
||||
|
this.currentNode = node; |
||||
|
|
||||
|
// Updates the code path due to node's position in its parent node.
|
||||
|
if (node.parent) { |
||||
|
preprocess(this, node); |
||||
|
} |
||||
|
|
||||
|
// Updates the code path.
|
||||
|
// And emits onCodePathStart/onCodePathSegmentStart events.
|
||||
|
processCodePathToEnter(this, node); |
||||
|
|
||||
|
// Emits node events.
|
||||
|
this.original.enterNode(node); |
||||
|
|
||||
|
this.currentNode = null; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Does the process to leave a given AST node. |
||||
|
* This updates state of analysis and calls `leaveNode` of the wrapped. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node which is leaving. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
leaveNode: function(node) { |
||||
|
this.currentNode = node; |
||||
|
|
||||
|
// Updates the code path.
|
||||
|
// And emits onCodePathStart/onCodePathSegmentStart events.
|
||||
|
processCodePathToExit(this, node); |
||||
|
|
||||
|
// Emits node events.
|
||||
|
this.original.leaveNode(node); |
||||
|
|
||||
|
// Emits the last onCodePathStart/onCodePathSegmentStart events.
|
||||
|
postprocess(this, node); |
||||
|
|
||||
|
this.currentNode = null; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* This is called on a code path looped. |
||||
|
* Then this raises a looped event. |
||||
|
* |
||||
|
* @param {CodePathSegment} fromSegment - A segment of prev. |
||||
|
* @param {CodePathSegment} toSegment - A segment of next. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
onLooped: function(fromSegment, toSegment) { |
||||
|
if (fromSegment.reachable && toSegment.reachable) { |
||||
|
debug.dump("onCodePathSegmentLoop " + fromSegment.id + " -> " + toSegment.id); |
||||
|
this.emitter.emit( |
||||
|
"onCodePathSegmentLoop", |
||||
|
fromSegment, |
||||
|
toSegment, |
||||
|
this.currentNode |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
module.exports = CodePathAnalyzer; |
@ -0,0 +1,208 @@ |
|||||
|
/** |
||||
|
* @fileoverview A class of the code path segment. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var assert = require("assert"), |
||||
|
debug = require("./debug-helpers"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Replaces unused segments with the previous segments of each unused segment. |
||||
|
* |
||||
|
* @param {CodePathSegment[]} segments - An array of segments to replace. |
||||
|
* @returns {CodePathSegment[]} The replaced array. |
||||
|
*/ |
||||
|
function flattenUnusedSegments(segments) { |
||||
|
var done = Object.create(null); |
||||
|
var retv = []; |
||||
|
|
||||
|
for (var i = 0; i < segments.length; ++i) { |
||||
|
var segment = segments[i]; |
||||
|
|
||||
|
// Ignores duplicated.
|
||||
|
if (done[segment.id]) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Use previous segments if unused.
|
||||
|
if (!segment.internal.used) { |
||||
|
for (var j = 0; j < segment.allPrevSegments.length; ++j) { |
||||
|
var prevSegment = segment.allPrevSegments[j]; |
||||
|
|
||||
|
if (!done[prevSegment.id]) { |
||||
|
done[prevSegment.id] = true; |
||||
|
retv.push(prevSegment); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
done[segment.id] = true; |
||||
|
retv.push(segment); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return retv; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given segment is reachable. |
||||
|
* |
||||
|
* @param {CodePathSegment} segment - A segment to check. |
||||
|
* @returns {boolean} `true` if the segment is reachable. |
||||
|
*/ |
||||
|
function isReachable(segment) { |
||||
|
return segment.reachable; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* A code path segment. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @param {string} id - An identifier. |
||||
|
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments. |
||||
|
* This array includes unreachable segments. |
||||
|
* @param {boolean} reachable - A flag which shows this is reachable. |
||||
|
*/ |
||||
|
function CodePathSegment(id, allPrevSegments, reachable) { |
||||
|
/** |
||||
|
* The identifier of this code path. |
||||
|
* Rules use it to store additional information of each rule. |
||||
|
* @type {string} |
||||
|
*/ |
||||
|
this.id = id; |
||||
|
|
||||
|
/** |
||||
|
* An array of the next segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
this.nextSegments = []; |
||||
|
|
||||
|
/** |
||||
|
* An array of the previous segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
this.prevSegments = allPrevSegments.filter(isReachable); |
||||
|
|
||||
|
/** |
||||
|
* An array of the next segments. |
||||
|
* This array includes unreachable segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
this.allNextSegments = []; |
||||
|
|
||||
|
/** |
||||
|
* An array of the previous segments. |
||||
|
* This array includes unreachable segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
this.allPrevSegments = allPrevSegments; |
||||
|
|
||||
|
/** |
||||
|
* A flag which shows this is reachable. |
||||
|
* @type {boolean} |
||||
|
*/ |
||||
|
this.reachable = reachable; |
||||
|
|
||||
|
// Internal data.
|
||||
|
Object.defineProperty(this, "internal", {value: { |
||||
|
used: false |
||||
|
}}); |
||||
|
|
||||
|
/* istanbul ignore if */ |
||||
|
if (debug.enabled) { |
||||
|
this.internal.nodes = []; |
||||
|
this.internal.exitNodes = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates the root segment. |
||||
|
* |
||||
|
* @param {string} id - An identifier. |
||||
|
* @returns {CodePathSegment} The created segment. |
||||
|
*/ |
||||
|
CodePathSegment.newRoot = function(id) { |
||||
|
return new CodePathSegment(id, [], true); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Creates a segment that follows given segments. |
||||
|
* |
||||
|
* @param {string} id - An identifier. |
||||
|
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments. |
||||
|
* @returns {CodePathSegment} The created segment. |
||||
|
*/ |
||||
|
CodePathSegment.newNext = function(id, allPrevSegments) { |
||||
|
return new CodePathSegment( |
||||
|
id, |
||||
|
flattenUnusedSegments(allPrevSegments), |
||||
|
allPrevSegments.some(isReachable)); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Creates an unreachable segment that follows given segments. |
||||
|
* |
||||
|
* @param {string} id - An identifier. |
||||
|
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments. |
||||
|
* @returns {CodePathSegment} The created segment. |
||||
|
*/ |
||||
|
CodePathSegment.newUnreachable = function(id, allPrevSegments) { |
||||
|
return new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Creates a segment that follows given segments. |
||||
|
* This factory method does not connect with `allPrevSegments`. |
||||
|
* But this inherits `reachable` flag. |
||||
|
* |
||||
|
* @param {string} id - An identifier. |
||||
|
* @param {CodePathSegment[]} allPrevSegments - An array of the previous segments. |
||||
|
* @returns {CodePathSegment} The created segment. |
||||
|
*/ |
||||
|
CodePathSegment.newDisconnected = function(id, allPrevSegments) { |
||||
|
return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Makes a given segment being used. |
||||
|
* |
||||
|
* And this function registers the segment into the previous segments as a next. |
||||
|
* |
||||
|
* @param {CodePathSegment} segment - A segment to mark. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
CodePathSegment.markUsed = function(segment) { |
||||
|
assert(!segment.internal.used, segment.id + " is marked twice."); |
||||
|
segment.internal.used = true; |
||||
|
|
||||
|
var i; |
||||
|
if (segment.reachable) { |
||||
|
for (i = 0; i < segment.allPrevSegments.length; ++i) { |
||||
|
var prevSegment = segment.allPrevSegments[i]; |
||||
|
|
||||
|
prevSegment.allNextSegments.push(segment); |
||||
|
prevSegment.nextSegments.push(segment); |
||||
|
} |
||||
|
} else { |
||||
|
for (i = 0; i < segment.allPrevSegments.length; ++i) { |
||||
|
segment.allPrevSegments[i].allNextSegments.push(segment); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
module.exports = CodePathSegment; |
File diff suppressed because it is too large
@ -0,0 +1,118 @@ |
|||||
|
/** |
||||
|
* @fileoverview A class of the code path. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var CodePathState = require("./code-path-state"); |
||||
|
var IdGenerator = require("./id-generator"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* A code path. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @param {string} id - An identifier. |
||||
|
* @param {CodePath|null} upper - The code path of the upper function scope. |
||||
|
* @param {function} onLooped - A callback function to notify looping. |
||||
|
*/ |
||||
|
function CodePath(id, upper, onLooped) { |
||||
|
/** |
||||
|
* The identifier of this code path. |
||||
|
* Rules use it to store additional information of each rule. |
||||
|
* @type {string} |
||||
|
*/ |
||||
|
this.id = id; |
||||
|
|
||||
|
/** |
||||
|
* The code path of the upper function scope. |
||||
|
* @type {CodePath|null} |
||||
|
*/ |
||||
|
this.upper = upper; |
||||
|
|
||||
|
/** |
||||
|
* The code paths of nested function scopes. |
||||
|
* @type {CodePath[]} |
||||
|
*/ |
||||
|
this.childCodePaths = []; |
||||
|
|
||||
|
// Initializes internal state.
|
||||
|
Object.defineProperty( |
||||
|
this, |
||||
|
"internal", |
||||
|
{value: new CodePathState(new IdGenerator(id + "_"), onLooped)}); |
||||
|
|
||||
|
// Adds this into `childCodePaths` of `upper`.
|
||||
|
if (upper) { |
||||
|
upper.childCodePaths.push(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
CodePath.prototype = { |
||||
|
constructor: CodePath, |
||||
|
|
||||
|
/** |
||||
|
* The initial code path segment. |
||||
|
* @type {CodePathSegment} |
||||
|
*/ |
||||
|
get initialSegment() { |
||||
|
return this.internal.initialSegment; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Final code path segments. |
||||
|
* This array is a mix of `returnedSegments` and `thrownSegments`. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
get finalSegments() { |
||||
|
return this.internal.finalSegments; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Final code path segments which is with `return` statements. |
||||
|
* This array contains the last path segment if it's reachable. |
||||
|
* Since the reachable last path returns `undefined`. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
get returnedSegments() { |
||||
|
return this.internal.returnedForkContext; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Final code path segments which is with `throw` statements. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
get thrownSegments() { |
||||
|
return this.internal.thrownForkContext; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Current code path segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
get currentSegments() { |
||||
|
return this.internal.currentSegments; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Gets the state of a given code path. |
||||
|
* |
||||
|
* @param {CodePath} codePath - A code path to get. |
||||
|
* @returns {CodePathState} The state of the code path. |
||||
|
*/ |
||||
|
CodePath.getState = function getState(codePath) { |
||||
|
return codePath.internal; |
||||
|
}; |
||||
|
|
||||
|
module.exports = CodePath; |
@ -0,0 +1,197 @@ |
|||||
|
/** |
||||
|
* @fileoverview Helpers to debug for code path analysis. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var debug = require("debug")("eslint:code-path"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Gets id of a given segment. |
||||
|
* @param {CodePathSegment} segment - A segment to get. |
||||
|
* @returns {string} Id of the segment. |
||||
|
*/ |
||||
|
/* istanbul ignore next */ |
||||
|
function getId(segment) { // eslint-disable-line require-jsdoc
|
||||
|
return segment.id + (segment.reachable ? "" : "!"); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
/** |
||||
|
* A flag that debug dumping is enabled or not. |
||||
|
* @type {boolean} |
||||
|
*/ |
||||
|
enabled: debug.enabled, |
||||
|
|
||||
|
/** |
||||
|
* Dumps given objects. |
||||
|
* |
||||
|
* @param {...any} args - objects to dump. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
dump: debug, |
||||
|
|
||||
|
/** |
||||
|
* Dumps the current analyzing state. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to dump. |
||||
|
* @param {CodePathState} state - A state to dump. |
||||
|
* @param {boolean} leaving - A flag whether or not it's leaving |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { |
||||
|
for (var i = 0; i < state.currentSegments.length; ++i) { |
||||
|
var segInternal = state.currentSegments[i].internal; |
||||
|
if (leaving) { |
||||
|
segInternal.exitNodes.push(node); |
||||
|
} else { |
||||
|
segInternal.nodes.push(node); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
debug( |
||||
|
state.currentSegments.map(getId).join(",") + ") " + |
||||
|
node.type + (leaving ? ":exit" : "") |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Dumps a DOT code of a given code path. |
||||
|
* The DOT code can be visialized with Graphvis. |
||||
|
* |
||||
|
* @param {CodePath} codePath - A code path to dump. |
||||
|
* @returns {void} |
||||
|
* @see http://www.graphviz.org
|
||||
|
* @see http://www.webgraphviz.com
|
||||
|
*/ |
||||
|
dumpDot: !debug.enabled ? debug : /* istanbul ignore next */ function(codePath) { |
||||
|
var text = |
||||
|
"\n" + |
||||
|
"digraph {\n" + |
||||
|
"node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" + |
||||
|
"initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; |
||||
|
|
||||
|
if (codePath.returnedSegments.length > 0) { |
||||
|
text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; |
||||
|
} |
||||
|
if (codePath.thrownSegments.length > 0) { |
||||
|
text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize];\n"; |
||||
|
} |
||||
|
|
||||
|
var traceMap = Object.create(null); |
||||
|
var arrows = this.makeDotArrows(codePath, traceMap); |
||||
|
|
||||
|
for (var id in traceMap) { // eslint-disable-line guard-for-in
|
||||
|
var segment = traceMap[id]; |
||||
|
|
||||
|
text += id + "["; |
||||
|
|
||||
|
if (segment.reachable) { |
||||
|
text += "label=\""; |
||||
|
} else { |
||||
|
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n"; |
||||
|
} |
||||
|
|
||||
|
if (segment.internal.nodes.length > 0) { |
||||
|
text += segment.internal.nodes.map(function(node) { |
||||
|
switch (node.type) { |
||||
|
case "Identifier": return node.type + " (" + node.name + ")"; |
||||
|
case "Literal": return node.type + " (" + node.value + ")"; |
||||
|
default: return node.type; |
||||
|
} |
||||
|
}).join("\\n"); |
||||
|
} else if (segment.internal.exitNodes.length > 0) { |
||||
|
text += segment.internal.exitNodes.map(function(node) { |
||||
|
switch (node.type) { |
||||
|
case "Identifier": return node.type + ":exit (" + node.name + ")"; |
||||
|
case "Literal": return node.type + ":exit (" + node.value + ")"; |
||||
|
default: return node.type + ":exit"; |
||||
|
} |
||||
|
}).join("\\n"); |
||||
|
} else { |
||||
|
text += "????"; |
||||
|
} |
||||
|
|
||||
|
text += "\"];\n"; |
||||
|
} |
||||
|
|
||||
|
text += arrows + "\n"; |
||||
|
text += "}"; |
||||
|
debug("DOT", text); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Makes a DOT code of a given code path. |
||||
|
* The DOT code can be visialized with Graphvis. |
||||
|
* |
||||
|
* @param {CodePath} codePath - A code path to make DOT. |
||||
|
* @param {object} traceMap - Optional. A map to check whether or not segments had been done. |
||||
|
* @returns {string} A DOT code of the code path. |
||||
|
*/ |
||||
|
makeDotArrows: function(codePath, traceMap) { |
||||
|
var stack = [[codePath.initialSegment, 0]]; |
||||
|
var done = traceMap || Object.create(null); |
||||
|
var lastId = codePath.initialSegment.id; |
||||
|
var text = "initial->" + codePath.initialSegment.id; |
||||
|
|
||||
|
while (stack.length > 0) { |
||||
|
var item = stack.pop(); |
||||
|
var segment = item[0]; |
||||
|
var index = item[1]; |
||||
|
if (done[segment.id] && index === 0) { |
||||
|
continue; |
||||
|
} |
||||
|
done[segment.id] = segment; |
||||
|
|
||||
|
var nextSegment = segment.allNextSegments[index]; |
||||
|
if (!nextSegment) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (lastId === segment.id) { |
||||
|
text += "->" + nextSegment.id; |
||||
|
} else { |
||||
|
text += ";\n" + segment.id + "->" + nextSegment.id; |
||||
|
} |
||||
|
lastId = nextSegment.id; |
||||
|
|
||||
|
stack.unshift([segment, 1 + index]); |
||||
|
stack.push([nextSegment, 0]); |
||||
|
} |
||||
|
|
||||
|
codePath.returnedSegments.forEach(function(finalSegment) { |
||||
|
if (lastId === finalSegment.id) { |
||||
|
text += "->final"; |
||||
|
} else { |
||||
|
text += ";\n" + finalSegment.id + "->final"; |
||||
|
} |
||||
|
lastId = null; |
||||
|
}); |
||||
|
|
||||
|
codePath.thrownSegments.forEach(function(finalSegment) { |
||||
|
if (lastId === finalSegment.id) { |
||||
|
text += "->thrown"; |
||||
|
} else { |
||||
|
text += ";\n" + finalSegment.id + "->thrown"; |
||||
|
} |
||||
|
lastId = null; |
||||
|
}); |
||||
|
|
||||
|
return text + ";"; |
||||
|
} |
||||
|
}; |
@ -0,0 +1,258 @@ |
|||||
|
/** |
||||
|
* @fileoverview A class to operate forking. |
||||
|
* |
||||
|
* This is state of forking. |
||||
|
* This has a fork list and manages it. |
||||
|
* |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var assert = require("assert"), |
||||
|
CodePathSegment = require("./code-path-segment"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Gets whether or not a given segment is reachable. |
||||
|
* |
||||
|
* @param {CodePathSegment} segment - A segment to get. |
||||
|
* @returns {boolean} `true` if the segment is reachable. |
||||
|
*/ |
||||
|
function isReachable(segment) { |
||||
|
return segment.reachable; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates new segments from the specific range of `context.segmentsList`. |
||||
|
* |
||||
|
* When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and |
||||
|
* `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`. |
||||
|
* This `h` is from `b`, `d`, and `f`. |
||||
|
* |
||||
|
* @param {ForkContext} context - An instance. |
||||
|
* @param {number} begin - The first index of the previous segments. |
||||
|
* @param {number} end - The last index of the previous segments. |
||||
|
* @param {function} create - A factory function of new segments. |
||||
|
* @returns {CodePathSegment[]} New segments. |
||||
|
*/ |
||||
|
function makeSegments(context, begin, end, create) { |
||||
|
var list = context.segmentsList; |
||||
|
if (begin < 0) { |
||||
|
begin = list.length + begin; |
||||
|
} |
||||
|
if (end < 0) { |
||||
|
end = list.length + end; |
||||
|
} |
||||
|
|
||||
|
var segments = []; |
||||
|
for (var i = 0; i < context.count; ++i) { |
||||
|
var allPrevSegments = []; |
||||
|
|
||||
|
for (var j = begin; j <= end; ++j) { |
||||
|
allPrevSegments.push(list[j][i]); |
||||
|
} |
||||
|
|
||||
|
segments.push(create(context.idGenerator.next(), allPrevSegments)); |
||||
|
} |
||||
|
|
||||
|
return segments; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* `segments` becomes doubly in a `finally` block. Then if a code path exits by a |
||||
|
* control statement (such as `break`, `continue`) from the `finally` block, the |
||||
|
* destination's segments may be half of the source segments. In that case, this |
||||
|
* merges segments. |
||||
|
* |
||||
|
* @param {ForkContext} context - An instance. |
||||
|
* @param {CodePathSegment[]} segments - Segments to merge. |
||||
|
* @returns {CodePathSegment[]} The merged segments. |
||||
|
*/ |
||||
|
function mergeExtraSegments(context, segments) { |
||||
|
while (segments.length > context.count) { |
||||
|
var merged = []; |
||||
|
for (var i = 0, length = segments.length / 2 | 0; i < length; ++i) { |
||||
|
merged.push(CodePathSegment.newNext( |
||||
|
context.idGenerator.next(), |
||||
|
[segments[i], segments[i + length]] |
||||
|
)); |
||||
|
} |
||||
|
segments = merged; |
||||
|
} |
||||
|
return segments; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* A class to manage forking. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @param {IdGenerator} idGenerator - An identifier generator for segments. |
||||
|
* @param {ForkContext|null} upper - An upper fork context. |
||||
|
* @param {number} count - A number of parallel segments. |
||||
|
*/ |
||||
|
function ForkContext(idGenerator, upper, count) { |
||||
|
this.idGenerator = idGenerator; |
||||
|
this.upper = upper; |
||||
|
this.count = count; |
||||
|
this.segmentsList = []; |
||||
|
} |
||||
|
|
||||
|
ForkContext.prototype = { |
||||
|
constructor: ForkContext, |
||||
|
|
||||
|
/** |
||||
|
* The head segments. |
||||
|
* @type {CodePathSegment[]} |
||||
|
*/ |
||||
|
get head() { |
||||
|
var list = this.segmentsList; |
||||
|
return list.length === 0 ? [] : list[list.length - 1]; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* A flag which shows empty. |
||||
|
* @type {boolean} |
||||
|
*/ |
||||
|
get empty() { |
||||
|
return this.segmentsList.length === 0; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* A flag which shows reachable. |
||||
|
* @type {boolean} |
||||
|
*/ |
||||
|
get reachable() { |
||||
|
var segments = this.head; |
||||
|
return segments.length > 0 && segments.some(isReachable); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates new segments from this context. |
||||
|
* |
||||
|
* @param {number} begin - The first index of previous segments. |
||||
|
* @param {number} end - The last index of previous segments. |
||||
|
* @returns {CodePathSegment[]} New segments. |
||||
|
*/ |
||||
|
makeNext: function(begin, end) { |
||||
|
return makeSegments(this, begin, end, CodePathSegment.newNext); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates new segments from this context. |
||||
|
* The new segments is always unreachable. |
||||
|
* |
||||
|
* @param {number} begin - The first index of previous segments. |
||||
|
* @param {number} end - The last index of previous segments. |
||||
|
* @returns {CodePathSegment[]} New segments. |
||||
|
*/ |
||||
|
makeUnreachable: function(begin, end) { |
||||
|
return makeSegments(this, begin, end, CodePathSegment.newUnreachable); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates new segments from this context. |
||||
|
* The new segments don't have connections for previous segments. |
||||
|
* But these inherit the reachable flag from this context. |
||||
|
* |
||||
|
* @param {number} begin - The first index of previous segments. |
||||
|
* @param {number} end - The last index of previous segments. |
||||
|
* @returns {CodePathSegment[]} New segments. |
||||
|
*/ |
||||
|
makeDisconnected: function(begin, end) { |
||||
|
return makeSegments(this, begin, end, CodePathSegment.newDisconnected); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Adds segments into this context. |
||||
|
* The added segments become the head. |
||||
|
* |
||||
|
* @param {CodePathSegment[]} segments - Segments to add. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
add: function(segments) { |
||||
|
assert(segments.length >= this.count, segments.length + " >= " + this.count); |
||||
|
|
||||
|
this.segmentsList.push(mergeExtraSegments(this, segments)); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Replaces the head segments with given segments. |
||||
|
* The current head segments are removed. |
||||
|
* |
||||
|
* @param {CodePathSegment[]} segments - Segments to add. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
replaceHead: function(segments) { |
||||
|
assert(segments.length >= this.count, segments.length + " >= " + this.count); |
||||
|
|
||||
|
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments)); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Adds all segments of a given fork context into this context. |
||||
|
* |
||||
|
* @param {ForkContext} context - A fork context to add. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
addAll: function(context) { |
||||
|
assert(context.count === this.count); |
||||
|
|
||||
|
var source = context.segmentsList; |
||||
|
for (var i = 0; i < source.length; ++i) { |
||||
|
this.segmentsList.push(source[i]); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Clears all secments in this context. |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
clear: function() { |
||||
|
this.segmentsList = []; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Creates the root fork context. |
||||
|
* |
||||
|
* @param {IdGenerator} idGenerator - An identifier generator for segments. |
||||
|
* @returns {ForkContext} New fork context. |
||||
|
*/ |
||||
|
ForkContext.newRoot = function(idGenerator) { |
||||
|
var context = new ForkContext(idGenerator, null, 1); |
||||
|
|
||||
|
context.add([CodePathSegment.newRoot(idGenerator.next())]); |
||||
|
|
||||
|
return context; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Creates an empty fork context preceded by a given context. |
||||
|
* |
||||
|
* @param {ForkContext} parentContext - The parent fork context. |
||||
|
* @param {boolean} forkLeavingPath - A flag which shows inside of `finally` block. |
||||
|
* @returns {ForkContext} New fork context. |
||||
|
*/ |
||||
|
ForkContext.newEmpty = function(parentContext, forkLeavingPath) { |
||||
|
return new ForkContext( |
||||
|
parentContext.idGenerator, |
||||
|
parentContext, |
||||
|
(forkLeavingPath ? 2 : 1) * parentContext.count); |
||||
|
}; |
||||
|
|
||||
|
module.exports = ForkContext; |
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* @fileoverview A class of identifiers generator for code path segments. |
||||
|
* |
||||
|
* Each rule uses the identifier of code path segments to store additional |
||||
|
* information of the code path. |
||||
|
* |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* A generator for unique ids. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @param {string} prefix - Optional. A prefix of generated ids. |
||||
|
*/ |
||||
|
function IdGenerator(prefix) { |
||||
|
this.prefix = String(prefix); |
||||
|
this.n = 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Generates id. |
||||
|
* |
||||
|
* @returns {string} A generated id. |
||||
|
*/ |
||||
|
IdGenerator.prototype.next = function() { |
||||
|
this.n = 1 + this.n | 0; |
||||
|
/* istanbul ignore if */ |
||||
|
if (this.n < 0) { |
||||
|
this.n = 1; |
||||
|
} |
||||
|
return this.prefix + this.n; |
||||
|
}; |
||||
|
|
||||
|
module.exports = IdGenerator; |
@ -0,0 +1,336 @@ |
|||||
|
/** |
||||
|
* @fileoverview Used for creating a suggested configuration based on project code. |
||||
|
* @author Ian VanSchooten |
||||
|
* @copyright 2015 Ian VanSchooten. All rights reserved. |
||||
|
* See LICENSE in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var lodash = require("lodash"), |
||||
|
debug = require("debug"), |
||||
|
eslint = require("../eslint"), |
||||
|
configRule = require("./config-rule"), |
||||
|
recConfig = require("../../conf/eslint.json"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Data
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
|
||||
|
RECOMMENDED_CONFIG_NAME = "eslint:recommended"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Private
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
debug = debug("eslint:autoconfig"); |
||||
|
|
||||
|
/** |
||||
|
* Information about a rule configuration, in the context of a Registry. |
||||
|
* |
||||
|
* @typedef {Object} registryItem |
||||
|
* @param {ruleConfig} config A valid configuration for the rule |
||||
|
* @param {number} specificity The number of elements in the ruleConfig array |
||||
|
* @param {number} errorCount The number of errors encountered when linting with the config |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* This callback is used to measure execution status in a progress bar |
||||
|
* @callback progressCallback |
||||
|
* @param {number} The total number of times the callback will be called. |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Create registryItems for rules |
||||
|
* @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items |
||||
|
* @returns {Object} registryItems for each rule in provided rulesConfig |
||||
|
*/ |
||||
|
function makeRegistryItems(rulesConfig) { |
||||
|
return Object.keys(rulesConfig).reduce(function(accumulator, ruleId) { |
||||
|
accumulator[ruleId] = rulesConfig[ruleId].map(function(config) { |
||||
|
return { |
||||
|
config: config, |
||||
|
specificity: config.length || 1, |
||||
|
errorCount: void 0 |
||||
|
}; |
||||
|
}); |
||||
|
return accumulator; |
||||
|
}, {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates an object in which to store rule configs and error counts |
||||
|
* |
||||
|
* Unless a rulesConfig is provided at construction, the registry will not contain |
||||
|
* any rules, only methods. This will be useful for building up registries manually. |
||||
|
* |
||||
|
* @constructor |
||||
|
* @class Registry |
||||
|
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations |
||||
|
*/ |
||||
|
function Registry(rulesConfig) { |
||||
|
this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {}; |
||||
|
} |
||||
|
|
||||
|
Registry.prototype = { |
||||
|
|
||||
|
constructor: Registry, |
||||
|
|
||||
|
/** |
||||
|
* Populate the registry with core rule configs. |
||||
|
* |
||||
|
* It will set the registry's `rule` property to an object having rule names |
||||
|
* as keys and an array of registryItems as values. |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
populateFromCoreRules: function() { |
||||
|
var rulesConfig = configRule.createCoreRuleConfigs(); |
||||
|
this.rules = makeRegistryItems(rulesConfig); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates sets of rule configurations which can be used for linting |
||||
|
* and initializes registry errors to zero for those configurations (side effect). |
||||
|
* |
||||
|
* This combines as many rules together as possible, such that the first sets |
||||
|
* in the array will have the highest number of rules configured, and later sets |
||||
|
* will have fewer and fewer, as not all rules have the same number of possible |
||||
|
* configurations. |
||||
|
* |
||||
|
* The length of the returned array will be <= MAX_CONFIG_COMBINATIONS. |
||||
|
* |
||||
|
* @param {Object} registry The autoconfig registry |
||||
|
* @returns {Object[]} "rules" configurations to use for linting |
||||
|
*/ |
||||
|
buildRuleSets: function() { |
||||
|
var idx = 0, |
||||
|
ruleIds = Object.keys(this.rules), |
||||
|
ruleSets = []; |
||||
|
|
||||
|
/** |
||||
|
* Add a rule configuration from the registry to the ruleSets |
||||
|
* |
||||
|
* This is broken out into its own function so that it doesn't need to be |
||||
|
* created inside of the while loop. |
||||
|
* |
||||
|
* @param {string} rule The ruleId to add. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
var addRuleToRuleSet = function(rule) { |
||||
|
// This check ensures that there is a rule configuration, and that
|
||||
|
// it either has fewer than the max cominbations allowed, or if it has
|
||||
|
// too many configs, we will only use the most basic of them.
|
||||
|
var hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS); |
||||
|
if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) { |
||||
|
// If the rule has too many possible combinations, only take simple ones, avoiding objects.
|
||||
|
if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") { |
||||
|
return; |
||||
|
} |
||||
|
ruleSets[idx] = ruleSets[idx] || {}; |
||||
|
ruleSets[idx][rule] = this.rules[rule][idx].config; |
||||
|
// Initialize errorCount to zero, since this is a config which will be linted
|
||||
|
this.rules[rule][idx].errorCount = 0; |
||||
|
} |
||||
|
}.bind(this); |
||||
|
|
||||
|
while (ruleSets.length === idx) { |
||||
|
ruleIds.forEach(addRuleToRuleSet); |
||||
|
idx += 1; |
||||
|
} |
||||
|
|
||||
|
return ruleSets; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Remove all items from the registry with a non-zero number of errors |
||||
|
* |
||||
|
* Note: this also removes rule configurations which were not linted |
||||
|
* (meaning, they have an undefined errorCount). |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
stripFailingConfigs: function() { |
||||
|
var ruleIds = Object.keys(this.rules), |
||||
|
newRegistry = new Registry(); |
||||
|
|
||||
|
newRegistry.rules = lodash.assign({}, this.rules); |
||||
|
ruleIds.forEach(function(ruleId) { |
||||
|
var errorFreeItems = newRegistry.rules[ruleId].filter(function(registryItem) { |
||||
|
return (registryItem.errorCount === 0); |
||||
|
}); |
||||
|
if (errorFreeItems.length > 0) { |
||||
|
newRegistry.rules[ruleId] = errorFreeItems; |
||||
|
} else { |
||||
|
delete newRegistry.rules[ruleId]; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return newRegistry; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Removes rule configurations which were not included in a ruleSet |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
stripExtraConfigs: function() { |
||||
|
var ruleIds = Object.keys(this.rules), |
||||
|
newRegistry = new Registry(); |
||||
|
|
||||
|
newRegistry.rules = lodash.assign({}, this.rules); |
||||
|
ruleIds.forEach(function(ruleId) { |
||||
|
newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(function(registryItem) { |
||||
|
return (typeof registryItem.errorCount !== "undefined"); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
return newRegistry; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Creates a registry of rules which had no error-free configs. |
||||
|
* The new registry is intended to be analyzed to determine whether its rules |
||||
|
* should be disabled or set to warning. |
||||
|
* |
||||
|
* @returns {Registry} A registry of failing rules. |
||||
|
*/ |
||||
|
getFailingRulesRegistry: function() { |
||||
|
var ruleIds = Object.keys(this.rules), |
||||
|
failingRegistry = new Registry(); |
||||
|
|
||||
|
ruleIds.forEach(function(ruleId) { |
||||
|
var failingConfigs = this.rules[ruleId].filter(function(registryItem) { |
||||
|
return (registryItem.errorCount > 0); |
||||
|
}); |
||||
|
if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) { |
||||
|
failingRegistry.rules[ruleId] = failingConfigs; |
||||
|
} |
||||
|
}.bind(this)); |
||||
|
|
||||
|
return failingRegistry; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Create an eslint config for any rules which only have one configuration |
||||
|
* in the registry. |
||||
|
* |
||||
|
* @returns {Object} An eslint config with rules section populated |
||||
|
*/ |
||||
|
createConfig: function() { |
||||
|
var ruleIds = Object.keys(this.rules), |
||||
|
config = {rules: {}}; |
||||
|
|
||||
|
ruleIds.forEach(function(ruleId) { |
||||
|
if (this.rules[ruleId].length === 1) { |
||||
|
config.rules[ruleId] = this.rules[ruleId][0].config; |
||||
|
} |
||||
|
}.bind(this)); |
||||
|
|
||||
|
return config; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Return a cloned registry containing only configs with a desired specificity |
||||
|
* |
||||
|
* @param {number} specificity Only keep configs with this specificity |
||||
|
* @returns {Registry} A registry of rules |
||||
|
*/ |
||||
|
filterBySpecificity: function(specificity) { |
||||
|
var ruleIds = Object.keys(this.rules), |
||||
|
newRegistry = new Registry(); |
||||
|
|
||||
|
newRegistry.rules = lodash.assign({}, this.rules); |
||||
|
ruleIds.forEach(function(ruleId) { |
||||
|
newRegistry.rules[ruleId] = this.rules[ruleId].filter(function(registryItem) { |
||||
|
return (registryItem.specificity === specificity); |
||||
|
}); |
||||
|
}.bind(this)); |
||||
|
|
||||
|
return newRegistry; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Lint SourceCodes against all configurations in the registry, and record results |
||||
|
* |
||||
|
* @param {Object[]} sourceCodes SourceCode objects for each filename |
||||
|
* @param {Object} config ESLint config object |
||||
|
* @param {progressCallback} [cb] Optional callback for reporting execution status |
||||
|
* @returns {Registry} New registry with errorCount populated |
||||
|
*/ |
||||
|
lintSourceCode: function(sourceCodes, config, cb) { |
||||
|
var totalFilesLinting, |
||||
|
lintConfig, |
||||
|
ruleSets, |
||||
|
ruleSetIdx, |
||||
|
filenames, |
||||
|
lintedRegistry; |
||||
|
|
||||
|
lintedRegistry = new Registry(); |
||||
|
lintedRegistry.rules = lodash.assign({}, this.rules); |
||||
|
ruleSets = lintedRegistry.buildRuleSets(); |
||||
|
lintedRegistry = lintedRegistry.stripExtraConfigs(); |
||||
|
|
||||
|
debug("Linting with all possible rule combinations"); |
||||
|
filenames = Object.keys(sourceCodes); |
||||
|
totalFilesLinting = filenames.length * ruleSets.length; |
||||
|
filenames.forEach(function(filename) { |
||||
|
debug("Linting file: " + filename); |
||||
|
ruleSetIdx = 0; |
||||
|
ruleSets.forEach(function(ruleSet) { |
||||
|
lintConfig = lodash.assign({}, config, {rules: ruleSet}); |
||||
|
var lintResults = eslint.verify(sourceCodes[filename], lintConfig); |
||||
|
lintResults.forEach(function(result) { |
||||
|
lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1; |
||||
|
}); |
||||
|
ruleSetIdx += 1; |
||||
|
if (cb) { |
||||
|
cb(totalFilesLinting); // eslint-disable-line callback-return
|
||||
|
} |
||||
|
}); |
||||
|
// Deallocate for GC
|
||||
|
sourceCodes[filename] = null; |
||||
|
}); |
||||
|
|
||||
|
return lintedRegistry; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Extract rule configuration into eslint:recommended where possible. |
||||
|
* |
||||
|
* This will return a new config with `"extends": "eslint:recommended"` and |
||||
|
* only the rules which have configurations different from the recommended config. |
||||
|
* |
||||
|
* @param {Object} config config object |
||||
|
* @returns {Object} config object using `"extends": "eslint:recommended"` |
||||
|
*/ |
||||
|
function extendFromRecommended(config) { |
||||
|
var newConfig = lodash.assign({}, config); |
||||
|
var recRules = Object.keys(recConfig.rules).filter(function(ruleId) { |
||||
|
return (recConfig.rules[ruleId] === 2 || recConfig.rules[ruleId][0] === 2); |
||||
|
}); |
||||
|
|
||||
|
recRules.forEach(function(ruleId) { |
||||
|
if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) { |
||||
|
delete newConfig.rules[ruleId]; |
||||
|
} |
||||
|
}); |
||||
|
newConfig.extends = RECOMMENDED_CONFIG_NAME; |
||||
|
return newConfig; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
Registry: Registry, |
||||
|
extendFromRecommended: extendFromRecommended |
||||
|
}; |
@ -0,0 +1,308 @@ |
|||||
|
/** |
||||
|
* @fileoverview Create configurations for a rule |
||||
|
* @author Ian VanSchooten |
||||
|
* @copyright 2016 Ian VanSchooten. All rights reserved. |
||||
|
* See LICENSE in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var rules = require("../rules"), |
||||
|
loadRules = require("../load-rules"); |
||||
|
|
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Wrap all of the elements of an array into arrays. |
||||
|
* @param {*[]} xs Any array. |
||||
|
* @returns {Array[]} An array of arrays. |
||||
|
*/ |
||||
|
function explodeArray(xs) { |
||||
|
return xs.reduce(function(accumulator, x) { |
||||
|
accumulator.push([x]); |
||||
|
return accumulator; |
||||
|
}, []); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mix two arrays such that each element of the second array is concatenated |
||||
|
* onto each element of the first array. |
||||
|
* |
||||
|
* For example: |
||||
|
* combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]]
|
||||
|
* |
||||
|
* @param {array} arr1 The first array to combine. |
||||
|
* @param {array} arr2 The second array to combine. |
||||
|
* @returns {array} A mixture of the elements of the first and second arrays. |
||||
|
*/ |
||||
|
function combineArrays(arr1, arr2) { |
||||
|
var res = []; |
||||
|
if (arr1.length === 0) { |
||||
|
return explodeArray(arr2); |
||||
|
} |
||||
|
if (arr2.length === 0) { |
||||
|
return explodeArray(arr1); |
||||
|
} |
||||
|
arr1.forEach(function(x1) { |
||||
|
arr2.forEach(function(x2) { |
||||
|
res.push([].concat(x1, x2)); |
||||
|
}); |
||||
|
}); |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Group together valid rule configurations based on object properties |
||||
|
* |
||||
|
* e.g.: |
||||
|
* groupByProperty([ |
||||
|
* {before: true}, |
||||
|
* {before: false}, |
||||
|
* {after: true}, |
||||
|
* {after: false} |
||||
|
* ]); |
||||
|
* |
||||
|
* will return: |
||||
|
* [ |
||||
|
* [{before: true}, {before: false}], |
||||
|
* [{after: true}, {after: false}] |
||||
|
* ] |
||||
|
* |
||||
|
* @param {Object[]} objects Array of objects, each with one property/value pair |
||||
|
* @returns {Array[]} Array of arrays of objects grouped by property |
||||
|
*/ |
||||
|
function groupByProperty(objects) { |
||||
|
var groupedObj = objects.reduce(function(accumulator, obj) { |
||||
|
var prop = Object.keys(obj)[0]; |
||||
|
accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj]; |
||||
|
return accumulator; |
||||
|
}, {}); |
||||
|
return Object.keys(groupedObj).map(function(prop) { |
||||
|
return groupedObj[prop]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Private
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Configuration settings for a rule. |
||||
|
* |
||||
|
* A configuration can be a single number (severity), or an array where the first |
||||
|
* element in the array is the severity, and is the only required element. |
||||
|
* Configs may also have one or more additional elements to specify rule |
||||
|
* configuration or options. |
||||
|
* |
||||
|
* @typedef {array|number} ruleConfig |
||||
|
* @param {number} 0 The rule's severity (0, 1, 2). |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Object whose keys are rule names and values are arrays of valid ruleConfig items |
||||
|
* which should be linted against the target source code to determine error counts. |
||||
|
* (a ruleConfigSet.ruleConfigs). |
||||
|
* |
||||
|
* e.g. rulesConfig = { |
||||
|
* "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]], |
||||
|
* "no-console": [2] |
||||
|
* } |
||||
|
* @typedef rulesConfig |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Create valid rule configurations by combining two arrays, |
||||
|
* with each array containing multiple objects each with a |
||||
|
* single property/value pair and matching properties. |
||||
|
* |
||||
|
* e.g.: |
||||
|
* combinePropertyObjects( |
||||
|
* [{before: true}, {before: false}], |
||||
|
* [{after: true}, {after: false}] |
||||
|
* ); |
||||
|
* |
||||
|
* will return: |
||||
|
* [ |
||||
|
* {before: true, after: true}, |
||||
|
* {before: true, after: false}, |
||||
|
* {before: false, after: true}, |
||||
|
* {before: false, after: false} |
||||
|
* ] |
||||
|
* |
||||
|
* @param {Object[]} objArr1 Single key/value objects, all with the same key |
||||
|
* @param {Object[]} objArr2 Single key/value objects, all with another key |
||||
|
* @returns {Object[]} Combined objects for each combination of input properties and values |
||||
|
*/ |
||||
|
function combinePropertyObjects(objArr1, objArr2) { |
||||
|
var res = []; |
||||
|
if (objArr1.length === 0) { |
||||
|
return objArr2; |
||||
|
} |
||||
|
if (objArr2.length === 0) { |
||||
|
return objArr1; |
||||
|
} |
||||
|
objArr1.forEach(function(obj1) { |
||||
|
objArr2.forEach(function(obj2) { |
||||
|
var combinedObj = {}; |
||||
|
var obj1Props = Object.keys(obj1); |
||||
|
var obj2Props = Object.keys(obj2); |
||||
|
obj1Props.forEach(function(prop1) { |
||||
|
combinedObj[prop1] = obj1[prop1]; |
||||
|
}); |
||||
|
obj2Props.forEach(function(prop2) { |
||||
|
combinedObj[prop2] = obj2[prop2]; |
||||
|
}); |
||||
|
res.push(combinedObj); |
||||
|
}); |
||||
|
}); |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates a new instance of a rule configuration set |
||||
|
* |
||||
|
* A rule configuration set is an array of configurations that are valid for a |
||||
|
* given rule. For example, the configuration set for the "semi" rule could be: |
||||
|
* |
||||
|
* ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
|
||||
|
* |
||||
|
* @param {ruleConfig[]} configs Valid rule configurations |
||||
|
* @constructor |
||||
|
*/ |
||||
|
function RuleConfigSet(configs) { |
||||
|
|
||||
|
/** |
||||
|
* Stored valid rule configurations for this instance |
||||
|
* @type {array} |
||||
|
*/ |
||||
|
this.ruleConfigs = configs || []; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
RuleConfigSet.prototype = { |
||||
|
|
||||
|
constructor: RuleConfigSet, |
||||
|
|
||||
|
/** |
||||
|
* Add a severity level to the front of all configs in the instance. |
||||
|
* This should only be called after all configs have been added to the instance. |
||||
|
* |
||||
|
* @param {number} [severity=2] The level of severity for the rule (0, 1, 2) |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
addErrorSeverity: function(severity) { |
||||
|
severity = severity || 2; |
||||
|
this.ruleConfigs = this.ruleConfigs.map(function(config) { |
||||
|
config.unshift(severity); |
||||
|
return config; |
||||
|
}); |
||||
|
// Add a single config at the beginning consisting of only the severity
|
||||
|
this.ruleConfigs.unshift(severity); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Add rule configs from an array of strings (schema enums) |
||||
|
* @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
addEnums: function(enums) { |
||||
|
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums)); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Add rule configurations from a schema object |
||||
|
* @param {Object} obj Schema item with type === "object" |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
addObject: function(obj) { |
||||
|
var objectConfigSet = { |
||||
|
objectConfigs: [], |
||||
|
add: function(property, values) { |
||||
|
var optionObj; |
||||
|
for (var idx = 0; idx < values.length; idx++) { |
||||
|
optionObj = {}; |
||||
|
optionObj[property] = values[idx]; |
||||
|
this.objectConfigs.push(optionObj); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
combine: function() { |
||||
|
this.objectConfigs = groupByProperty(this.objectConfigs).reduce(function(accumulator, objArr) { |
||||
|
return combinePropertyObjects(accumulator, objArr); |
||||
|
}, []); |
||||
|
} |
||||
|
}; |
||||
|
// The object schema could have multiple independent properties.
|
||||
|
// If any contain enums or booleans, they can be added and then combined
|
||||
|
Object.keys(obj.properties).forEach(function(prop) { |
||||
|
if (obj.properties[prop].enum) { |
||||
|
objectConfigSet.add(prop, obj.properties[prop].enum); |
||||
|
} |
||||
|
if (obj.properties[prop].type && obj.properties[prop].type === "boolean") { |
||||
|
objectConfigSet.add(prop, [true, false]); |
||||
|
} |
||||
|
}); |
||||
|
objectConfigSet.combine(); |
||||
|
|
||||
|
if (objectConfigSet.objectConfigs.length > 0) { |
||||
|
this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs)); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Generate valid rule configurations based on a schema object |
||||
|
* @param {Object} schema A rule's schema object |
||||
|
* @returns {array[]} Valid rule configurations |
||||
|
*/ |
||||
|
function generateConfigsFromSchema(schema) { |
||||
|
var configSet = new RuleConfigSet(); |
||||
|
if (Array.isArray(schema)) { |
||||
|
schema.forEach(function(opt) { |
||||
|
if (opt.enum) { |
||||
|
configSet.addEnums(opt.enum); |
||||
|
} |
||||
|
if (opt.type && opt.type === "object") { |
||||
|
configSet.addObject(opt); |
||||
|
} |
||||
|
if (opt.oneOf) { |
||||
|
// TODO (IanVS): not yet implemented
|
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
configSet.addErrorSeverity(); |
||||
|
return configSet.ruleConfigs; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Generate possible rule configurations for all of the core rules |
||||
|
* @returns {rulesConfig} Hash of rule names and arrays of possible configurations |
||||
|
*/ |
||||
|
function createCoreRuleConfigs() { |
||||
|
var ruleList = loadRules(); |
||||
|
return Object.keys(ruleList).reduce(function(accumulator, id) { |
||||
|
var rule = rules.get(id); |
||||
|
var schema = (typeof rule === "function") ? rule.schema : rule.meta.schema; |
||||
|
accumulator[id] = generateConfigsFromSchema(schema); |
||||
|
return accumulator; |
||||
|
}, {}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
generateConfigsFromSchema: generateConfigsFromSchema, |
||||
|
createCoreRuleConfigs: createCoreRuleConfigs |
||||
|
}; |
@ -0,0 +1,87 @@ |
|||||
|
/** |
||||
|
* @fileoverview Environments manager |
||||
|
* @author Nicholas C. Zakas |
||||
|
* @copyright 2016 Nicholas C. Zakas. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var debug = require("debug"), |
||||
|
envs = require("../../conf/environments"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Private
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
debug = debug("eslint:enviroments"); |
||||
|
|
||||
|
var environments = Object.create(null); |
||||
|
|
||||
|
/** |
||||
|
* Loads the default environments. |
||||
|
* @returns {void} |
||||
|
* @private |
||||
|
*/ |
||||
|
function load() { |
||||
|
Object.keys(envs).forEach(function(envName) { |
||||
|
environments[envName] = envs[envName]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// always load default environments upfront
|
||||
|
load(); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
|
||||
|
load: load, |
||||
|
|
||||
|
/** |
||||
|
* Gets the environment with the given name. |
||||
|
* @param {string} name The name of the environment to retrieve. |
||||
|
* @returns {Object?} The environment object or null if not found. |
||||
|
*/ |
||||
|
get: function(name) { |
||||
|
return environments[name] || null; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Defines an environment. |
||||
|
* @param {string} name The name of the environment. |
||||
|
* @param {Object} env The environment settings. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
define: function(name, env) { |
||||
|
environments[name] = env; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Imports all environments from a plugin. |
||||
|
* @param {Object} plugin The plugin object. |
||||
|
* @param {string} pluginName The name of the plugin. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
importPlugin: function(plugin, pluginName) { |
||||
|
if (plugin.environments) { |
||||
|
Object.keys(plugin.environments).forEach(function(envName) { |
||||
|
this.define(pluginName + "/" + envName, plugin.environments[envName]); |
||||
|
}, this); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Resets all environments. Only use for tests! |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
testReset: function() { |
||||
|
environments = Object.create(null); |
||||
|
load(); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,144 @@ |
|||||
|
/** |
||||
|
* @fileoverview Plugins manager |
||||
|
* @author Nicholas C. Zakas |
||||
|
* @copyright 2016 Nicholas C. Zakas. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var debug = require("debug"), |
||||
|
Environments = require("./environments"), |
||||
|
rules = require("../rules"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Private
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
debug = debug("eslint:plugins"); |
||||
|
|
||||
|
var plugins = Object.create(null); |
||||
|
|
||||
|
var PLUGIN_NAME_PREFIX = "eslint-plugin-", |
||||
|
NAMESPACE_REGEX = /^@.*\//i; |
||||
|
|
||||
|
/** |
||||
|
* Removes the prefix `eslint-plugin-` from a plugin name. |
||||
|
* @param {string} pluginName The name of the plugin which may have the prefix. |
||||
|
* @returns {string} The name of the plugin without prefix. |
||||
|
*/ |
||||
|
function removePrefix(pluginName) { |
||||
|
return pluginName.indexOf(PLUGIN_NAME_PREFIX) === 0 ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the scope (namespace) of a plugin. |
||||
|
* @param {string} pluginName The name of the plugin which may have the prefix. |
||||
|
* @returns {string} The name of the plugins namepace if it has one. |
||||
|
*/ |
||||
|
function getNamespace(pluginName) { |
||||
|
return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : ""; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Removes the namespace from a plugin name. |
||||
|
* @param {string} pluginName The name of the plugin which may have the prefix. |
||||
|
* @returns {string} The name of the plugin without the namespace. |
||||
|
*/ |
||||
|
function removeNamespace(pluginName) { |
||||
|
return pluginName.replace(NAMESPACE_REGEX, ""); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = { |
||||
|
|
||||
|
removePrefix: removePrefix, |
||||
|
getNamespace: getNamespace, |
||||
|
removeNamespace: removeNamespace, |
||||
|
|
||||
|
/** |
||||
|
* Defines a plugin with a given name rather than loading from disk. |
||||
|
* @param {string} pluginName The name of the plugin to load. |
||||
|
* @param {Object} plugin The plugin object. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
define: function(pluginName, plugin) { |
||||
|
var pluginNameWithoutNamespace = removeNamespace(pluginName), |
||||
|
pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace); |
||||
|
|
||||
|
plugins[pluginNameWithoutPrefix] = plugin; |
||||
|
|
||||
|
// load up environments and rules
|
||||
|
Environments.importPlugin(plugin, pluginNameWithoutPrefix); |
||||
|
|
||||
|
if (plugin.rules) { |
||||
|
rules.import(plugin.rules, pluginNameWithoutPrefix); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Gets a plugin with the given name. |
||||
|
* @param {string} pluginName The name of the plugin to retrieve. |
||||
|
* @returns {Object} The plugin or null if not loaded. |
||||
|
*/ |
||||
|
get: function(pluginName) { |
||||
|
return plugins[pluginName] || null; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Returns all plugins that are loaded. |
||||
|
* @returns {Object} The plugins cache. |
||||
|
*/ |
||||
|
getAll: function() { |
||||
|
return plugins; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Loads a plugin with the given name. |
||||
|
* @param {string} pluginName The name of the plugin to load. |
||||
|
* @returns {void} |
||||
|
* @throws {Error} If the plugin cannot be loaded. |
||||
|
*/ |
||||
|
load: function(pluginName) { |
||||
|
var pluginNamespace = getNamespace(pluginName), |
||||
|
pluginNameWithoutNamespace = removeNamespace(pluginName), |
||||
|
pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace), |
||||
|
plugin = null; |
||||
|
|
||||
|
if (!plugins[pluginNameWithoutPrefix]) { |
||||
|
try { |
||||
|
plugin = require(pluginNamespace + PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix); |
||||
|
} catch (err) { |
||||
|
debug("Failed to load plugin eslint-plugin-" + pluginNameWithoutPrefix + ". Proceeding without it."); |
||||
|
err.message = "Failed to load plugin " + pluginName + ": " + err.message; |
||||
|
throw err; |
||||
|
} |
||||
|
|
||||
|
this.define(pluginName, plugin); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Loads all plugins from an array. |
||||
|
* @param {string[]} pluginNames An array of plugins names. |
||||
|
* @returns {void} |
||||
|
* @throws {Error} If a plugin cannot be loaded. |
||||
|
*/ |
||||
|
loadAll: function(pluginNames) { |
||||
|
pluginNames.forEach(this.load, this); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Resets plugin information. Use for tests only. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
testReset: function() { |
||||
|
plugins = Object.create(null); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,8 @@ |
|||||
|
<tr style="display:none" class="f-<%= parentIndex %>"> |
||||
|
<td><%= lineNumber %>:<%= columnNumber %></td> |
||||
|
<td class="clr-<%= severityNumber %>"><%= severityName %></td> |
||||
|
<td><%- message %></td> |
||||
|
<td> |
||||
|
<a href="http://eslint.org/docs/rules/<%= ruleId %>" target="_blank"><%= ruleId %></a> |
||||
|
</td> |
||||
|
</tr> |
@ -0,0 +1,113 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<head> |
||||
|
<title>ESLint Report</title> |
||||
|
<style> |
||||
|
body { |
||||
|
font-family:Arial, "Helvetica Neue", Helvetica, sans-serif; |
||||
|
font-size:16px; |
||||
|
font-weight:normal; |
||||
|
margin:0; |
||||
|
padding:0; |
||||
|
color:#333 |
||||
|
} |
||||
|
#overview { |
||||
|
padding:20px 30px |
||||
|
} |
||||
|
td, th { |
||||
|
padding:5px 10px |
||||
|
} |
||||
|
h1 { |
||||
|
margin:0 |
||||
|
} |
||||
|
table { |
||||
|
margin:30px; |
||||
|
width:calc(100% - 60px); |
||||
|
max-width:1000px; |
||||
|
border-radius:5px; |
||||
|
border:1px solid #ddd; |
||||
|
border-spacing:0px; |
||||
|
} |
||||
|
th { |
||||
|
font-weight:400; |
||||
|
font-size:normal; |
||||
|
text-align:left; |
||||
|
cursor:pointer |
||||
|
} |
||||
|
td.clr-1, td.clr-2, th span { |
||||
|
font-weight:700 |
||||
|
} |
||||
|
th span { |
||||
|
float:right; |
||||
|
margin-left:20px |
||||
|
} |
||||
|
th span:after { |
||||
|
content:""; |
||||
|
clear:both; |
||||
|
display:block |
||||
|
} |
||||
|
tr:last-child td { |
||||
|
border-bottom:none |
||||
|
} |
||||
|
tr td:first-child, tr td:last-child { |
||||
|
color:#9da0a4 |
||||
|
} |
||||
|
#overview.bg-0, tr.bg-0 th { |
||||
|
color:#468847; |
||||
|
background:#dff0d8; |
||||
|
border-bottom:1px solid #d6e9c6 |
||||
|
} |
||||
|
#overview.bg-1, tr.bg-1 th { |
||||
|
color:#f0ad4e; |
||||
|
background:#fcf8e3; |
||||
|
border-bottom:1px solid #fbeed5 |
||||
|
} |
||||
|
#overview.bg-2, tr.bg-2 th { |
||||
|
color:#b94a48; |
||||
|
background:#f2dede; |
||||
|
border-bottom:1px solid #eed3d7 |
||||
|
} |
||||
|
td { |
||||
|
border-bottom:1px solid #ddd |
||||
|
} |
||||
|
td.clr-1 { |
||||
|
color:#f0ad4e |
||||
|
} |
||||
|
td.clr-2 { |
||||
|
color:#b94a48 |
||||
|
} |
||||
|
td a { |
||||
|
color:#3a33d1; |
||||
|
text-decoration:none |
||||
|
} |
||||
|
td a:hover { |
||||
|
color:#272296; |
||||
|
text-decoration:underline |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="overview" class="bg-<%= reportColor %>"> |
||||
|
<h1>ESLint Report</h1> |
||||
|
<div> |
||||
|
<span><%= reportSummary %></span> - Generated on <%= date %> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table> |
||||
|
<tbody> |
||||
|
<%= results %> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<script type="text/javascript"> |
||||
|
var groups = document.querySelectorAll("tr[data-group]"); |
||||
|
for (i = 0; i < groups.length; i++) { |
||||
|
groups[i].addEventListener("click", function() { |
||||
|
var inGroup = document.getElementsByClassName(this.getAttribute("data-group")); |
||||
|
this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+"); |
||||
|
for (var j = 0; j < inGroup.length; j++) { |
||||
|
inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row"; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,6 @@ |
|||||
|
<tr class="bg-<%- color %>" data-group="f-<%- index %>"> |
||||
|
<th colspan="4"> |
||||
|
[+] <%- filePath %> |
||||
|
<span><%- summary %></span> |
||||
|
</th> |
||||
|
</tr> |
@ -1,130 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<head> |
|
||||
<title>ESLint Report</title> |
|
||||
<style> |
|
||||
body { |
|
||||
font-family:Arial, "Helvetica Neue", Helvetica, sans-serif; |
|
||||
font-size:16px; |
|
||||
font-weight:normal; |
|
||||
margin:0; |
|
||||
padding:0; |
|
||||
color:#333 |
|
||||
} |
|
||||
#overview { |
|
||||
padding:20px 30px |
|
||||
} |
|
||||
td, th { |
|
||||
padding:5px 10px |
|
||||
} |
|
||||
h1 { |
|
||||
margin:0 |
|
||||
} |
|
||||
table { |
|
||||
margin:30px; |
|
||||
width:calc(100% - 60px); |
|
||||
max-width:1000px; |
|
||||
border-radius:5px; |
|
||||
border:1px solid #ddd; |
|
||||
border-spacing:0px; |
|
||||
} |
|
||||
th { |
|
||||
font-weight:400; |
|
||||
font-size:normal; |
|
||||
text-align:left; |
|
||||
cursor:pointer |
|
||||
} |
|
||||
td.clr-1, td.clr-2, th span { |
|
||||
font-weight:700 |
|
||||
} |
|
||||
th span { |
|
||||
float:right; |
|
||||
margin-left:20px |
|
||||
} |
|
||||
th span:after { |
|
||||
content:""; |
|
||||
clear:both; |
|
||||
display:block |
|
||||
} |
|
||||
tr:last-child td { |
|
||||
border-bottom:none |
|
||||
} |
|
||||
tr td:first-child, tr td:last-child { |
|
||||
color:#9da0a4 |
|
||||
} |
|
||||
#overview.bg-0, tr.bg-0 th { |
|
||||
color:#468847; |
|
||||
background:#dff0d8; |
|
||||
border-bottom:1px solid #d6e9c6 |
|
||||
} |
|
||||
#overview.bg-1, tr.bg-1 th { |
|
||||
color:#f0ad4e; |
|
||||
background:#fcf8e3; |
|
||||
border-bottom:1px solid #fbeed5 |
|
||||
} |
|
||||
#overview.bg-2, tr.bg-2 th { |
|
||||
color:#b94a48; |
|
||||
background:#f2dede; |
|
||||
border-bottom:1px solid #eed3d7 |
|
||||
} |
|
||||
td { |
|
||||
border-bottom:1px solid #ddd |
|
||||
} |
|
||||
td.clr-1 { |
|
||||
color:#f0ad4e |
|
||||
} |
|
||||
td.clr-2 { |
|
||||
color:#b94a48 |
|
||||
} |
|
||||
td a { |
|
||||
color:#3a33d1; |
|
||||
text-decoration:none |
|
||||
} |
|
||||
td a:hover { |
|
||||
color:#272296; |
|
||||
text-decoration:underline |
|
||||
} |
|
||||
</style> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div id="overview" class="bg-{{getColor totalErrors totalWarnings}}"> |
|
||||
<h1>ESLint Report</h1> |
|
||||
<div> |
|
||||
<span>{{renderText totalErrors totalWarnings}}</span> - Generated on {{date}} |
|
||||
</div> |
|
||||
</div> |
|
||||
<table> |
|
||||
<tbody> |
|
||||
{{#each results}} |
|
||||
<tr class="bg-{{getColor this.errorCount this.warningCount}}" data-group="f-{{@index}}"> |
|
||||
<th colspan="4"> |
|
||||
[+] {{this.filePath}} |
|
||||
<span>{{renderText this.errorCount this.warningCount}}</span> |
|
||||
</th> |
|
||||
</tr> |
|
||||
{{#each this.messages}} |
|
||||
<tr class="f-{{@../index}}" style="display:none"> |
|
||||
<td>{{#if this.line}}{{this.line}}{{else}}0{{/if}}:{{#if this.column}}{{this.column}}{{else}}0{{/if}}</td> |
|
||||
{{getSeverity this.severity}} |
|
||||
<td>{{this.message}}</td> |
|
||||
<td> |
|
||||
<a href="http://eslint.org/docs/rules/{{this.ruleId}}" target="_blank">{{this.ruleId}}</a> |
|
||||
</td> |
|
||||
</tr> |
|
||||
{{/each}} |
|
||||
{{/each}} |
|
||||
</tbody> |
|
||||
</table> |
|
||||
<script type="text/javascript"> |
|
||||
var groups = document.querySelectorAll("tr[data-group]"); |
|
||||
for (i = 0; i < groups.length; i++) { |
|
||||
groups[i].addEventListener("click", function() { |
|
||||
var inGroup = document.getElementsByClassName(this.getAttribute("data-group")); |
|
||||
this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+"); |
|
||||
for (var j = 0; j < inGroup.length; j++) { |
|
||||
inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row"; |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
</script> |
|
||||
</body> |
|
||||
</html> |
|
@ -0,0 +1,159 @@ |
|||||
|
/** |
||||
|
* @fileoverview "table reporter. |
||||
|
* @author Gajus Kuizinas <gajus@gajus.com> |
||||
|
* @copyright 2016 Gajus Kuizinas <gajus@gajus.com>. All rights reserved. |
||||
|
*/ |
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var chalk, |
||||
|
table, |
||||
|
pluralize; |
||||
|
|
||||
|
chalk = require("chalk"); |
||||
|
table = require("table").default; |
||||
|
pluralize = require("pluralize"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Draws text table. |
||||
|
* @param {Array<Object>} messages Error messages relating to a specific file. |
||||
|
* @returns {string} A text table. |
||||
|
*/ |
||||
|
function drawTable(messages) { |
||||
|
var rows; |
||||
|
|
||||
|
rows = []; |
||||
|
|
||||
|
if (messages.length === 0) { |
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
rows.push([ |
||||
|
chalk.bold("Line"), |
||||
|
chalk.bold("Column"), |
||||
|
chalk.bold("Type"), |
||||
|
chalk.bold("Message"), |
||||
|
chalk.bold("Rule ID") |
||||
|
]); |
||||
|
|
||||
|
messages.forEach(function(message) { |
||||
|
var messageType; |
||||
|
|
||||
|
if (message.fatal || message.severity === 2) { |
||||
|
messageType = chalk.red("error"); |
||||
|
} else { |
||||
|
messageType = chalk.yellow("warning"); |
||||
|
} |
||||
|
|
||||
|
rows.push([ |
||||
|
message.line || 0, |
||||
|
message.column || 0, |
||||
|
messageType, |
||||
|
message.message, |
||||
|
message.ruleId || "" |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
return table(rows, { |
||||
|
columns: { |
||||
|
0: { |
||||
|
width: 8, |
||||
|
wrapWord: true |
||||
|
}, |
||||
|
1: { |
||||
|
width: 8, |
||||
|
wrapWord: true |
||||
|
}, |
||||
|
2: { |
||||
|
width: 8, |
||||
|
wrapWord: true |
||||
|
}, |
||||
|
3: { |
||||
|
paddingRight: 5, |
||||
|
width: 50, |
||||
|
wrapWord: true |
||||
|
}, |
||||
|
4: { |
||||
|
width: 20, |
||||
|
wrapWord: true |
||||
|
} |
||||
|
}, |
||||
|
drawHorizontalLine: function(index) { |
||||
|
return index === 1; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Draws a report (multiple tables). |
||||
|
* @param {Array} results Report results for every file. |
||||
|
* @returns {string} A column of text tables. |
||||
|
*/ |
||||
|
function drawReport(results) { |
||||
|
var files; |
||||
|
|
||||
|
files = results.map(function(result) { |
||||
|
if (!result.messages.length) { |
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
return "\n" + result.filePath + "\n\n" + drawTable(result.messages); |
||||
|
}); |
||||
|
|
||||
|
files = files.filter(function(content) { |
||||
|
return content.trim(); |
||||
|
}); |
||||
|
|
||||
|
return files.join(""); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(report) { |
||||
|
var result, |
||||
|
errorCount, |
||||
|
warningCount; |
||||
|
|
||||
|
result = ""; |
||||
|
errorCount = 0; |
||||
|
warningCount = 0; |
||||
|
|
||||
|
report.forEach(function(fileReport) { |
||||
|
errorCount += fileReport.errorCount; |
||||
|
warningCount += fileReport.warningCount; |
||||
|
}); |
||||
|
|
||||
|
if (errorCount || warningCount) { |
||||
|
result = drawReport(report); |
||||
|
} |
||||
|
|
||||
|
result += "\n" + table([ |
||||
|
[ |
||||
|
chalk.red(pluralize("Error", errorCount, true)) |
||||
|
], |
||||
|
[ |
||||
|
chalk.yellow(pluralize("Warning", warningCount, true)) |
||||
|
] |
||||
|
], { |
||||
|
columns: { |
||||
|
0: { |
||||
|
width: 110, |
||||
|
wrapWord: true |
||||
|
} |
||||
|
}, |
||||
|
drawHorizontalLine: function() { |
||||
|
return true; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
}; |
@ -0,0 +1,64 @@ |
|||||
|
/** |
||||
|
* @fileoverview Visual Studio compatible formatter |
||||
|
* @author Ronald Pijnacker |
||||
|
* @copyright 2015 Ronald Pijnacker. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helper Functions
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Returns the severity of warning or error |
||||
|
* @param {object} message message object to examine |
||||
|
* @returns {string} severity level |
||||
|
* @private |
||||
|
*/ |
||||
|
function getMessageType(message) { |
||||
|
if (message.fatal || message.severity === 2) { |
||||
|
return "error"; |
||||
|
} else { |
||||
|
return "warning"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Public Interface
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(results) { |
||||
|
|
||||
|
var output = "", |
||||
|
total = 0; |
||||
|
|
||||
|
results.forEach(function(result) { |
||||
|
|
||||
|
var messages = result.messages; |
||||
|
total += messages.length; |
||||
|
|
||||
|
messages.forEach(function(message) { |
||||
|
|
||||
|
output += result.filePath; |
||||
|
output += "(" + (message.line || 0); |
||||
|
output += message.column ? "," + message.column : ""; |
||||
|
output += "): " + getMessageType(message); |
||||
|
output += message.ruleId ? " " + message.ruleId : ""; |
||||
|
output += " : " + message.message; |
||||
|
output += "\n"; |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
if (total === 0) { |
||||
|
output += "no problems"; |
||||
|
} else { |
||||
|
output += "\n" + total + " problem" + (total !== 1 ? "s" : ""); |
||||
|
} |
||||
|
|
||||
|
return output; |
||||
|
}; |
@ -0,0 +1,236 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to enforce return statements in callbacks of array's methods |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/; |
||||
|
var TARGET_METHODS = /^(?:every|filter|find(?:Index)?|map|reduce(?:Right)?|some|sort)$/; |
||||
|
|
||||
|
/** |
||||
|
* Checks a given code path segment is reachable. |
||||
|
* |
||||
|
* @param {CodePathSegment} segment - A segment to check. |
||||
|
* @returns {boolean} `true` if the segment is reachable. |
||||
|
*/ |
||||
|
function isReachable(segment) { |
||||
|
return segment.reachable; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets a readable location. |
||||
|
* |
||||
|
* - FunctionExpression -> the function name or `function` keyword. |
||||
|
* - ArrowFunctionExpression -> `=>` token. |
||||
|
* |
||||
|
* @param {ASTNode} node - A function node to get. |
||||
|
* @param {SourceCode} sourceCode - A source code to get tokens. |
||||
|
* @returns {ASTNode|Token} The node or the token of a location. |
||||
|
*/ |
||||
|
function getLocation(node, sourceCode) { |
||||
|
if (node.type === "ArrowFunctionExpression") { |
||||
|
return sourceCode.getTokenBefore(node.body); |
||||
|
} |
||||
|
return node.id || node; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the name of a given node if the node is a Identifier node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to get. |
||||
|
* @returns {string} The name of the node, or an empty string. |
||||
|
*/ |
||||
|
function getIdentifierName(node) { |
||||
|
return node.type === "Identifier" ? node.name : ""; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the value of a given node if the node is a Literal node or a |
||||
|
* TemplateLiteral node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to get. |
||||
|
* @returns {string} The value of the node, or an empty string. |
||||
|
*/ |
||||
|
function getConstantStringValue(node) { |
||||
|
switch (node.type) { |
||||
|
case "Literal": |
||||
|
return String(node.value); |
||||
|
|
||||
|
case "TemplateLiteral": |
||||
|
return node.expressions.length === 0 |
||||
|
? node.quasis[0].value.cooked |
||||
|
: ""; |
||||
|
|
||||
|
default: |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks a given node is a MemberExpression node which has the specified name's |
||||
|
* property. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @returns {boolean} `true` if the node is a MemberExpression node which has |
||||
|
* the specified name's property |
||||
|
*/ |
||||
|
function isTargetMethod(node) { |
||||
|
return ( |
||||
|
node.type === "MemberExpression" && |
||||
|
TARGET_METHODS.test( |
||||
|
(node.computed ? getConstantStringValue : getIdentifierName)(node.property) |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given node is a function expression which is the |
||||
|
* callback of an array method. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. This is one of |
||||
|
* FunctionExpression or ArrowFunctionExpression. |
||||
|
* @returns {boolean} `true` if the node is the callback of an array method. |
||||
|
*/ |
||||
|
function isCallbackOfArrayMethod(node) { |
||||
|
while (node) { |
||||
|
var parent = node.parent; |
||||
|
switch (parent.type) { |
||||
|
// Looks up the destination.
|
||||
|
// e.g.
|
||||
|
// foo.every(nativeFoo || function foo() { ... });
|
||||
|
case "LogicalExpression": |
||||
|
case "ConditionalExpression": |
||||
|
node = parent; |
||||
|
break; |
||||
|
|
||||
|
// If the upper function is IIFE, checks the destination of the return value.
|
||||
|
// e.g.
|
||||
|
// foo.every((function() {
|
||||
|
// // setup...
|
||||
|
// return function callback() { ... };
|
||||
|
// })());
|
||||
|
case "ReturnStatement": |
||||
|
var func = astUtils.getUpperFunction(parent); |
||||
|
if (func === null || !astUtils.isCallee(func)) { |
||||
|
return false; |
||||
|
} |
||||
|
node = func.parent; |
||||
|
break; |
||||
|
|
||||
|
// e.g.
|
||||
|
// Array.from([], function() {});
|
||||
|
// list.every(function() {});
|
||||
|
case "CallExpression": |
||||
|
if (astUtils.isArrayFromMethod(parent.callee)) { |
||||
|
return ( |
||||
|
parent.arguments.length >= 2 && |
||||
|
parent.arguments[1] === node |
||||
|
); |
||||
|
} |
||||
|
if (isTargetMethod(parent.callee)) { |
||||
|
return ( |
||||
|
parent.arguments.length >= 1 && |
||||
|
parent.arguments[0] === node |
||||
|
); |
||||
|
} |
||||
|
return false; |
||||
|
|
||||
|
// Otherwise this node is not target.
|
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* istanbul ignore next: unreachable */ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
var funcInfo = { |
||||
|
upper: null, |
||||
|
codePath: null, |
||||
|
hasReturn: false, |
||||
|
shouldCheck: false |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not the last code path segment is reachable. |
||||
|
* Then reports this function if the segment is reachable. |
||||
|
* |
||||
|
* If the last code path segment is reachable, there are paths which are not |
||||
|
* returned or thrown. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkLastSegment(node) { |
||||
|
if (funcInfo.shouldCheck && |
||||
|
funcInfo.codePath.currentSegments.some(isReachable) |
||||
|
) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: getLocation(node, context.getSourceCode()).loc.start, |
||||
|
message: funcInfo.hasReturn |
||||
|
? "Expected to return a value at the end of this function." |
||||
|
: "Expected to return a value in this function." |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
// Stacks this function's information.
|
||||
|
"onCodePathStart": function(codePath, node) { |
||||
|
funcInfo = { |
||||
|
upper: funcInfo, |
||||
|
codePath: codePath, |
||||
|
hasReturn: false, |
||||
|
shouldCheck: |
||||
|
TARGET_NODE_TYPE.test(node.type) && |
||||
|
node.body.type === "BlockStatement" && |
||||
|
isCallbackOfArrayMethod(node) |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
// Pops this function's information.
|
||||
|
"onCodePathEnd": function() { |
||||
|
funcInfo = funcInfo.upper; |
||||
|
}, |
||||
|
|
||||
|
// Checks the return statement is valid.
|
||||
|
"ReturnStatement": function(node) { |
||||
|
if (funcInfo.shouldCheck) { |
||||
|
funcInfo.hasReturn = true; |
||||
|
|
||||
|
if (!node.argument) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
message: "Expected a return value." |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Reports a given function if the last path is reachable.
|
||||
|
"FunctionExpression:exit": checkLastSegment, |
||||
|
"ArrowFunctionExpression:exit": checkLastSegment |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = []; |
@ -0,0 +1,110 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule that warns when identifier names that are |
||||
|
blacklisted in the configuration are used. |
||||
|
* @author Keith Cirkel (http://keithcirkel.co.uk)
|
||||
|
* Based on id-match rule: |
||||
|
* @author Matthieu Larcher |
||||
|
* @copyright 2015 Matthieu Larcher. All rights reserved. |
||||
|
* See LICENSE in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
|
||||
|
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//--------------------------------------------------------------------------
|
||||
|
|
||||
|
var blacklist = context.options; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Checks if a string matches the provided pattern |
||||
|
* @param {String} name The string to check. |
||||
|
* @returns {boolean} if the string is a match |
||||
|
* @private |
||||
|
*/ |
||||
|
function isInvalid(name) { |
||||
|
return blacklist.indexOf(name) !== -1; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Verifies if we should report an error or not based on the effective |
||||
|
* parent node and the identifier name. |
||||
|
* @param {ASTNode} effectiveParent The effective parent node of the node to be reported |
||||
|
* @param {String} name The identifier name of the identifier node |
||||
|
* @returns {boolean} whether an error should be reported or not |
||||
|
*/ |
||||
|
function shouldReport(effectiveParent, name) { |
||||
|
return effectiveParent.type !== "CallExpression" |
||||
|
&& effectiveParent.type !== "NewExpression" && |
||||
|
isInvalid(name); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports an AST node as a rule violation. |
||||
|
* @param {ASTNode} node The node to report. |
||||
|
* @returns {void} |
||||
|
* @private |
||||
|
*/ |
||||
|
function report(node) { |
||||
|
context.report(node, "Identifier '{{name}}' is blacklisted", { |
||||
|
name: node.name |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
|
||||
|
"Identifier": function(node) { |
||||
|
var name = node.name, |
||||
|
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; |
||||
|
|
||||
|
// MemberExpressions get special rules
|
||||
|
if (node.parent.type === "MemberExpression") { |
||||
|
|
||||
|
// Always check object names
|
||||
|
if (node.parent.object.type === "Identifier" && |
||||
|
node.parent.object.name === node.name) { |
||||
|
if (isInvalid(name)) { |
||||
|
report(node); |
||||
|
} |
||||
|
|
||||
|
// Report AssignmentExpressions only if they are the left side of the assignment
|
||||
|
} else if (effectiveParent.type === "AssignmentExpression" && |
||||
|
(effectiveParent.right.type !== "MemberExpression" || |
||||
|
effectiveParent.left.type === "MemberExpression" && |
||||
|
effectiveParent.left.property.name === node.name)) { |
||||
|
if (isInvalid(name)) { |
||||
|
report(node); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Properties have their own rules
|
||||
|
} else if (node.parent.type === "Property") { |
||||
|
|
||||
|
if (shouldReport(effectiveParent, name)) { |
||||
|
report(node); |
||||
|
} |
||||
|
|
||||
|
// Report anything that is a match and not a CallExpression
|
||||
|
} else if (shouldReport(effectiveParent, name)) { |
||||
|
report(node); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
}; |
||||
|
module.exports.schema = { |
||||
|
"type": "array", |
||||
|
"items": { |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"uniqueItems": true |
||||
|
}; |
@ -0,0 +1,520 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to enforce spacing before and after keywords. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils"), |
||||
|
keywords = require("../util/keywords"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Constants
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var PREV_TOKEN = /^[\)\]\}>]$/; |
||||
|
var NEXT_TOKEN = /^(?:[\(\[\{<~!]|\+\+?|--?)$/; |
||||
|
var PREV_TOKEN_M = /^[\)\]\}>*]$/; |
||||
|
var NEXT_TOKEN_M = /^[\{*]$/; |
||||
|
var TEMPLATE_OPEN_PAREN = /\$\{$/; |
||||
|
var TEMPLATE_CLOSE_PAREN = /^\}/; |
||||
|
var CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/; |
||||
|
var KEYS = keywords.concat(["as", "await", "from", "get", "let", "of", "set", "yield"]); |
||||
|
|
||||
|
// check duplications.
|
||||
|
(function() { |
||||
|
KEYS.sort(); |
||||
|
for (var i = 1; i < KEYS.length; ++i) { |
||||
|
if (KEYS[i] === KEYS[i - 1]) { |
||||
|
throw new Error("Duplication was found in the keyword list: " + KEYS[i]); |
||||
|
} |
||||
|
} |
||||
|
}()); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given token is a "Template" token ends with "${". |
||||
|
* |
||||
|
* @param {Token} token - A token to check. |
||||
|
* @returns {boolean} `true` if the token is a "Template" token ends with "${". |
||||
|
*/ |
||||
|
function isOpenParenOfTemplate(token) { |
||||
|
return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a given token is a "Template" token starts with "}". |
||||
|
* |
||||
|
* @param {Token} token - A token to check. |
||||
|
* @returns {boolean} `true` if the token is a "Template" token starts with "}". |
||||
|
*/ |
||||
|
function isCloseParenOfTemplate(token) { |
||||
|
return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if there are not space(s) before the token. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function expectSpaceBefore(token, pattern) { |
||||
|
pattern = pattern || PREV_TOKEN; |
||||
|
|
||||
|
var prevToken = sourceCode.getTokenBefore(token); |
||||
|
if (prevToken && |
||||
|
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && |
||||
|
!isOpenParenOfTemplate(prevToken) && |
||||
|
astUtils.isTokenOnSameLine(prevToken, token) && |
||||
|
!sourceCode.isSpaceBetweenTokens(prevToken, token) |
||||
|
) { |
||||
|
context.report({ |
||||
|
loc: token.loc.start, |
||||
|
message: "Expected space(s) before \"{{value}}\".", |
||||
|
data: token, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.insertTextBefore(token, " "); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if there are space(s) before the token. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function unexpectSpaceBefore(token, pattern) { |
||||
|
pattern = pattern || PREV_TOKEN; |
||||
|
|
||||
|
var prevToken = sourceCode.getTokenBefore(token); |
||||
|
if (prevToken && |
||||
|
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && |
||||
|
!isOpenParenOfTemplate(prevToken) && |
||||
|
astUtils.isTokenOnSameLine(prevToken, token) && |
||||
|
sourceCode.isSpaceBetweenTokens(prevToken, token) |
||||
|
) { |
||||
|
context.report({ |
||||
|
loc: token.loc.start, |
||||
|
message: "Unexpected space(s) before \"{{value}}\".", |
||||
|
data: token, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.removeRange([prevToken.range[1], token.range[0]]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if there are not space(s) after the token. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the next |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function expectSpaceAfter(token, pattern) { |
||||
|
pattern = pattern || NEXT_TOKEN; |
||||
|
|
||||
|
var nextToken = sourceCode.getTokenAfter(token); |
||||
|
if (nextToken && |
||||
|
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && |
||||
|
!isCloseParenOfTemplate(nextToken) && |
||||
|
astUtils.isTokenOnSameLine(token, nextToken) && |
||||
|
!sourceCode.isSpaceBetweenTokens(token, nextToken) |
||||
|
) { |
||||
|
context.report({ |
||||
|
loc: token.loc.start, |
||||
|
message: "Expected space(s) after \"{{value}}\".", |
||||
|
data: token, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.insertTextAfter(token, " "); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if there are space(s) after the token. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the next |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function unexpectSpaceAfter(token, pattern) { |
||||
|
pattern = pattern || NEXT_TOKEN; |
||||
|
|
||||
|
var nextToken = sourceCode.getTokenAfter(token); |
||||
|
if (nextToken && |
||||
|
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && |
||||
|
!isCloseParenOfTemplate(nextToken) && |
||||
|
astUtils.isTokenOnSameLine(token, nextToken) && |
||||
|
sourceCode.isSpaceBetweenTokens(token, nextToken) |
||||
|
) { |
||||
|
context.report({ |
||||
|
loc: token.loc.start, |
||||
|
message: "Unexpected space(s) after \"{{value}}\".", |
||||
|
data: token, |
||||
|
fix: function(fixer) { |
||||
|
return fixer.removeRange([token.range[1], nextToken.range[0]]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parses the option object and determines check methods for each keyword. |
||||
|
* |
||||
|
* @param {object|undefined} options - The option object to parse. |
||||
|
* @returns {object} - Normalized option object. |
||||
|
* Keys are keywords (there are for every keyword). |
||||
|
* Values are instances of `{"before": function, "after": function}`. |
||||
|
*/ |
||||
|
function parseOptions(options) { |
||||
|
var before = !options || options.before !== false; |
||||
|
var after = !options || options.after !== false; |
||||
|
var defaultValue = { |
||||
|
before: before ? expectSpaceBefore : unexpectSpaceBefore, |
||||
|
after: after ? expectSpaceAfter : unexpectSpaceAfter |
||||
|
}; |
||||
|
var overrides = (options && options.overrides) || {}; |
||||
|
var retv = Object.create(null); |
||||
|
|
||||
|
for (var i = 0; i < KEYS.length; ++i) { |
||||
|
var key = KEYS[i]; |
||||
|
var override = overrides[key]; |
||||
|
|
||||
|
if (override) { |
||||
|
var thisBefore = ("before" in override) ? override.before : before; |
||||
|
var thisAfter = ("after" in override) ? override.after : after; |
||||
|
retv[key] = { |
||||
|
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, |
||||
|
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter |
||||
|
}; |
||||
|
} else { |
||||
|
retv[key] = defaultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return retv; |
||||
|
} |
||||
|
|
||||
|
var checkMethodMap = parseOptions(context.options[0]); |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if usage of spacing followed by the token is |
||||
|
* invalid. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the previous |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingBefore(token, pattern) { |
||||
|
checkMethodMap[token.value].before(token, pattern); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if usage of spacing preceded by the token is |
||||
|
* invalid. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @param {RegExp|undefined} pattern - Optional. A pattern of the next |
||||
|
* token to check. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingAfter(token, pattern) { |
||||
|
checkMethodMap[token.value].after(token, pattern); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given token if usage of spacing around the token is invalid. |
||||
|
* |
||||
|
* @param {Token} token - A token to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingAround(token) { |
||||
|
checkSpacingBefore(token); |
||||
|
checkSpacingAfter(token); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports the first token of a given node if the first token is a keyword |
||||
|
* and usage of spacing around the token is invalid. |
||||
|
* |
||||
|
* @param {ASTNode|null} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingAroundFirstToken(node) { |
||||
|
var firstToken = node && sourceCode.getFirstToken(node); |
||||
|
if (firstToken && firstToken.type === "Keyword") { |
||||
|
checkSpacingAround(firstToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports the first token of a given node if the first token is a keyword |
||||
|
* and usage of spacing followed by the token is invalid. |
||||
|
* |
||||
|
* This is used for unary operators (e.g. `typeof`), `function`, and `super`. |
||||
|
* Other rules are handling usage of spacing preceded by those keywords. |
||||
|
* |
||||
|
* @param {ASTNode|null} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingBeforeFirstToken(node) { |
||||
|
var firstToken = node && sourceCode.getFirstToken(node); |
||||
|
if (firstToken && firstToken.type === "Keyword") { |
||||
|
checkSpacingBefore(firstToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports the previous token of a given node if the token is a keyword and |
||||
|
* usage of spacing around the token is invalid. |
||||
|
* |
||||
|
* @param {ASTNode|null} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingAroundTokenBefore(node) { |
||||
|
if (node) { |
||||
|
var token = sourceCode.getTokenBefore(node); |
||||
|
while (token.type !== "Keyword") { |
||||
|
token = sourceCode.getTokenBefore(token); |
||||
|
} |
||||
|
|
||||
|
checkSpacingAround(token); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `class` and `extends` keywords of a given node if usage of |
||||
|
* spacing around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForClass(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
checkSpacingAroundTokenBefore(node.superClass); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `if` and `else` keywords of a given node if usage of spacing |
||||
|
* around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForIfStatement(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
checkSpacingAroundTokenBefore(node.alternate); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `try`, `catch`, and `finally` keywords of a given node if usage |
||||
|
* of spacing around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForTryStatement(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
checkSpacingAroundFirstToken(node.handler); |
||||
|
checkSpacingAroundTokenBefore(node.finalizer); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `do` and `while` keywords of a given node if usage of spacing |
||||
|
* around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForDoWhileStatement(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
checkSpacingAroundTokenBefore(node.test); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `for` and `in` keywords of a given node if usage of spacing |
||||
|
* around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForForInStatement(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
checkSpacingAroundTokenBefore(node.right); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `for` and `of` keywords of a given node if usage of spacing |
||||
|
* around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForForOfStatement(node) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
|
||||
|
// `of` is not a keyword token.
|
||||
|
var token = sourceCode.getTokenBefore(node.right); |
||||
|
while (token.value !== "of") { |
||||
|
token = sourceCode.getTokenBefore(token); |
||||
|
} |
||||
|
checkSpacingAround(token); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `import`, `export`, `as`, and `from` keywords of a given node if |
||||
|
* usage of spacing around those keywords is invalid. |
||||
|
* |
||||
|
* This rule handles the `*` token in module declarations. |
||||
|
* |
||||
|
* import*as A from "./a"; /*error Expected space(s) after "import". |
||||
|
* error Expected space(s) before "as". |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForModuleDeclaration(node) { |
||||
|
var firstToken = sourceCode.getFirstToken(node); |
||||
|
checkSpacingBefore(firstToken, PREV_TOKEN_M); |
||||
|
checkSpacingAfter(firstToken, NEXT_TOKEN_M); |
||||
|
|
||||
|
if (node.source) { |
||||
|
var fromToken = sourceCode.getTokenBefore(node.source); |
||||
|
checkSpacingBefore(fromToken, PREV_TOKEN_M); |
||||
|
checkSpacingAfter(fromToken, NEXT_TOKEN_M); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `as` keyword of a given node if usage of spacing around this |
||||
|
* keyword is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForImportNamespaceSpecifier(node) { |
||||
|
var asToken = sourceCode.getFirstToken(node, 1); |
||||
|
checkSpacingBefore(asToken, PREV_TOKEN_M); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports `static`, `get`, and `set` keywords of a given node if usage of |
||||
|
* spacing around those keywords is invalid. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkSpacingForProperty(node) { |
||||
|
if (node.static) { |
||||
|
checkSpacingAroundFirstToken(node); |
||||
|
} |
||||
|
if (node.kind === "get" || node.kind === "set") { |
||||
|
var token = sourceCode.getFirstToken( |
||||
|
node, |
||||
|
node.static ? 1 : 0 |
||||
|
); |
||||
|
checkSpacingAround(token); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
// Statements
|
||||
|
DebuggerStatement: checkSpacingAroundFirstToken, |
||||
|
WithStatement: checkSpacingAroundFirstToken, |
||||
|
|
||||
|
// Statements - Control flow
|
||||
|
BreakStatement: checkSpacingAroundFirstToken, |
||||
|
ContinueStatement: checkSpacingAroundFirstToken, |
||||
|
ReturnStatement: checkSpacingAroundFirstToken, |
||||
|
ThrowStatement: checkSpacingAroundFirstToken, |
||||
|
TryStatement: checkSpacingForTryStatement, |
||||
|
|
||||
|
// Statements - Choice
|
||||
|
IfStatement: checkSpacingForIfStatement, |
||||
|
SwitchStatement: checkSpacingAroundFirstToken, |
||||
|
SwitchCase: checkSpacingAroundFirstToken, |
||||
|
|
||||
|
// Statements - Loops
|
||||
|
DoWhileStatement: checkSpacingForDoWhileStatement, |
||||
|
ForInStatement: checkSpacingForForInStatement, |
||||
|
ForOfStatement: checkSpacingForForOfStatement, |
||||
|
ForStatement: checkSpacingAroundFirstToken, |
||||
|
WhileStatement: checkSpacingAroundFirstToken, |
||||
|
|
||||
|
// Statements - Declarations
|
||||
|
ClassDeclaration: checkSpacingForClass, |
||||
|
ExportNamedDeclaration: checkSpacingForModuleDeclaration, |
||||
|
ExportDefaultDeclaration: checkSpacingAroundFirstToken, |
||||
|
ExportAllDeclaration: checkSpacingForModuleDeclaration, |
||||
|
FunctionDeclaration: checkSpacingBeforeFirstToken, |
||||
|
ImportDeclaration: checkSpacingForModuleDeclaration, |
||||
|
VariableDeclaration: checkSpacingAroundFirstToken, |
||||
|
|
||||
|
// Expressions
|
||||
|
ClassExpression: checkSpacingForClass, |
||||
|
FunctionExpression: checkSpacingBeforeFirstToken, |
||||
|
NewExpression: checkSpacingBeforeFirstToken, |
||||
|
Super: checkSpacingBeforeFirstToken, |
||||
|
ThisExpression: checkSpacingBeforeFirstToken, |
||||
|
UnaryExpression: checkSpacingBeforeFirstToken, |
||||
|
YieldExpression: checkSpacingBeforeFirstToken, |
||||
|
|
||||
|
// Others
|
||||
|
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, |
||||
|
MethodDefinition: checkSpacingForProperty, |
||||
|
Property: checkSpacingForProperty |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = [ |
||||
|
{ |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"before": {"type": "boolean"}, |
||||
|
"after": {"type": "boolean"}, |
||||
|
"overrides": { |
||||
|
"type": "object", |
||||
|
"properties": KEYS.reduce(function(retv, key) { |
||||
|
retv[key] = { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"before": {"type": "boolean"}, |
||||
|
"after": {"type": "boolean"} |
||||
|
}, |
||||
|
"additionalProperties": false |
||||
|
}; |
||||
|
return retv; |
||||
|
}, {}), |
||||
|
"additionalProperties": false |
||||
|
} |
||||
|
}, |
||||
|
"additionalProperties": false |
||||
|
} |
||||
|
]; |
@ -0,0 +1,113 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to ensure newline per method call when chaining calls |
||||
|
* @author Rajendra Patil |
||||
|
* @copyright 2016 Rajendra Patil. All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
|
||||
|
var options = context.options[0] || {}, |
||||
|
codeStateMap = {}, |
||||
|
ignoreChainWithDepth = options.ignoreChainWithDepth || 2; |
||||
|
|
||||
|
/** |
||||
|
* Check and Capture State if the chained calls/members |
||||
|
* @param {ASTNode} node The node to check |
||||
|
* @param {object} codeState The state of the code current code to be filled |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkAndCaptureStateRecursively(node, codeState) { |
||||
|
var valid = false, |
||||
|
objectLineNumber, |
||||
|
propertyLineNumber; |
||||
|
if (node.callee) { |
||||
|
node = node.callee; |
||||
|
codeState.hasFunctionCall = true; |
||||
|
} |
||||
|
|
||||
|
if (node.object) { |
||||
|
codeState.depth++; |
||||
|
|
||||
|
objectLineNumber = node.object.loc.end.line; |
||||
|
propertyLineNumber = node.property.loc.end.line; |
||||
|
valid = node.computed || propertyLineNumber > objectLineNumber; |
||||
|
|
||||
|
if (!valid) { |
||||
|
codeState.reports.push({ |
||||
|
node: node, |
||||
|
text: "Expected line break after `{{code}}`.", |
||||
|
depth: codeState.depth |
||||
|
}); |
||||
|
} |
||||
|
// Recurse
|
||||
|
checkAndCaptureStateRecursively(node.object, codeState); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
/** |
||||
|
* Verify and report the captured state report |
||||
|
* @param {object} codeState contains the captured state with `hasFunctionCall, reports and depth` |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportState(codeState) { |
||||
|
var report; |
||||
|
if (codeState.hasFunctionCall && codeState.depth > ignoreChainWithDepth && codeState.reports) { |
||||
|
while (codeState.reports.length) { |
||||
|
report = codeState.reports.shift(); |
||||
|
context.report(report.node, report.text, { |
||||
|
code: context.getSourceCode().getText(report.node.object).replace(/\r\n|\r|\n/g, "\\n") // Not to print newlines in error report
|
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize the node state object with default values. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function initializeState() { |
||||
|
return { |
||||
|
visited: false, |
||||
|
hasFunctionCall: false, |
||||
|
depth: 1, |
||||
|
reports: [] |
||||
|
}; |
||||
|
} |
||||
|
/** |
||||
|
* Process the said node and recurse internally |
||||
|
* @param {ASTNode} node The node to check |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function processNode(node) { |
||||
|
var stateKey = [node.loc.start.line, node.loc.start.column].join("@"), |
||||
|
codeState = codeStateMap[stateKey] = (codeStateMap[stateKey] || initializeState()); |
||||
|
if (!codeState.visited) { |
||||
|
codeState.visited = true; |
||||
|
checkAndCaptureStateRecursively(node, codeState); |
||||
|
} |
||||
|
reportState(codeState); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
"CallExpression": processNode, |
||||
|
"MemberExpression": processNode |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = [{ |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"ignoreChainWithDepth": { |
||||
|
"type": "integer", |
||||
|
"minimum": 1, |
||||
|
"maximum": 10 |
||||
|
} |
||||
|
}, |
||||
|
"additionalProperties": false |
||||
|
}]; |
@ -1,88 +0,0 @@ |
|||||
/** |
|
||||
* @fileoverview A rule to warn against using arrow functions in conditions. |
|
||||
* @author Jxck <https://github.com/Jxck>
|
|
||||
* @copyright 2015 Luke Karrys. All rights reserved. |
|
||||
* The MIT License (MIT) |
|
||||
|
|
||||
* Copyright (c) 2015 Jxck |
|
||||
|
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
* of this software and associated documentation files (the "Software"), to deal |
|
||||
* in the Software without restriction, including without limitation the rights |
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
* copies of the Software, and to permit persons to whom the Software is |
|
||||
* furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
* The above copyright notice and this permission notice shall be included in all |
|
||||
* copies or substantial portions of the Software. |
|
||||
|
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
* SOFTWARE. |
|
||||
*/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
// Helpers
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
|
|
||||
/** |
|
||||
* Checks whether or not a node is an arrow function expression. |
|
||||
* @param {ASTNode} node - node to test |
|
||||
* @returns {boolean} `true` if the node is an arrow function expression. |
|
||||
*/ |
|
||||
function isArrowFunction(node) { |
|
||||
return node.test && node.test.type === "ArrowFunctionExpression"; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Checks whether or not a node is a conditional expression. |
|
||||
* @param {ASTNode} node - node to test |
|
||||
* @returns {boolean} `true` if the node is a conditional expression. |
|
||||
*/ |
|
||||
function isConditional(node) { |
|
||||
return node.body && node.body.type === "ConditionalExpression"; |
|
||||
} |
|
||||
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
// Rule Definition
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
|
|
||||
module.exports = function(context) { |
|
||||
/** |
|
||||
* Reports if a conditional statement is an arrow function. |
|
||||
* @param {ASTNode} node - A node to check and report. |
|
||||
* @returns {void} |
|
||||
*/ |
|
||||
function checkCondition(node) { |
|
||||
if (isArrowFunction(node)) { |
|
||||
context.report(node, "Arrow function `=>` used inside {{statementType}} instead of comparison operator.", {statementType: node.type}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Reports if an arrow function contains an ambiguous conditional. |
|
||||
* @param {ASTNode} node - A node to check and report. |
|
||||
* @returns {void} |
|
||||
*/ |
|
||||
function checkArrowFunc(node) { |
|
||||
if (isConditional(node)) { |
|
||||
context.report(node, "Arrow function used ambiguously with a conditional expression."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
"IfStatement": checkCondition, |
|
||||
"WhileStatement": checkCondition, |
|
||||
"ForStatement": checkCondition, |
|
||||
"ConditionalExpression": checkCondition, |
|
||||
"ArrowFunctionExpression": checkArrowFunc |
|
||||
}; |
|
||||
}; |
|
||||
|
|
||||
module.exports.schema = []; |
|
@ -0,0 +1,65 @@ |
|||||
|
/** |
||||
|
* @fileoverview A rule to warn against using arrow functions when they could be |
||||
|
* confused with comparisions |
||||
|
* @author Jxck <https://github.com/Jxck>
|
||||
|
* @copyright 2015 Luke Karrys. All rights reserved. |
||||
|
* The MIT License (MIT) |
||||
|
|
||||
|
* Copyright (c) 2015 Jxck |
||||
|
|
||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||
|
* in the Software without restriction, including without limitation the rights |
||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||
|
* furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
* The above copyright notice and this permission notice shall be included in all |
||||
|
* copies or substantial portions of the Software. |
||||
|
|
||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
* SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Checks whether or not a node is a conditional expression. |
||||
|
* @param {ASTNode} node - node to test |
||||
|
* @returns {boolean} `true` if the node is a conditional expression. |
||||
|
*/ |
||||
|
function isConditional(node) { |
||||
|
return node.body && node.body.type === "ConditionalExpression"; |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
/** |
||||
|
* Reports if an arrow function contains an ambiguous conditional. |
||||
|
* @param {ASTNode} node - A node to check and report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function checkArrowFunc(node) { |
||||
|
if (isConditional(node)) { |
||||
|
context.report(node, "Arrow function used ambiguously with a conditional expression."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
"ArrowFunctionExpression": checkArrowFunc |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = []; |
@ -0,0 +1,149 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to disallow empty functions. |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2016 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var ALLOW_OPTIONS = Object.freeze([ |
||||
|
"functions", |
||||
|
"arrowFunctions", |
||||
|
"generatorFunctions", |
||||
|
"methods", |
||||
|
"generatorMethods", |
||||
|
"getters", |
||||
|
"setters", |
||||
|
"constructors" |
||||
|
]); |
||||
|
var SHOW_KIND = Object.freeze({ |
||||
|
functions: "function", |
||||
|
arrowFunctions: "arrow function", |
||||
|
generatorFunctions: "generator function", |
||||
|
asyncFunctions: "async function", |
||||
|
methods: "method", |
||||
|
generatorMethods: "generator method", |
||||
|
asyncMethods: "async method", |
||||
|
getters: "getter", |
||||
|
setters: "setter", |
||||
|
constructors: "constructor" |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* Gets the kind of a given function node. |
||||
|
* |
||||
|
* @param {ASTNode} node - A function node to get. This is one of |
||||
|
* an ArrowFunctionExpression, a FunctionDeclaration, or a |
||||
|
* FunctionExpression. |
||||
|
* @returns {string} The kind of the function. This is one of "functions", |
||||
|
* "arrowFunctions", "generatorFunctions", "asyncFunctions", "methods", |
||||
|
* "generatorMethods", "asyncMethods", "getters", "setters", and |
||||
|
* "constructors". |
||||
|
*/ |
||||
|
function getKind(node) { |
||||
|
var parent = node.parent; |
||||
|
var kind = ""; |
||||
|
|
||||
|
if (node.type === "ArrowFunctionExpression") { |
||||
|
return "arrowFunctions"; |
||||
|
} |
||||
|
|
||||
|
// Detects main kind.
|
||||
|
if (parent.type === "Property") { |
||||
|
if (parent.kind === "get") { |
||||
|
return "getters"; |
||||
|
} |
||||
|
if (parent.kind === "set") { |
||||
|
return "setters"; |
||||
|
} |
||||
|
kind = parent.method ? "methods" : "functions"; |
||||
|
|
||||
|
} else if (parent.type === "MethodDefinition") { |
||||
|
if (parent.kind === "get") { |
||||
|
return "getters"; |
||||
|
} |
||||
|
if (parent.kind === "set") { |
||||
|
return "setters"; |
||||
|
} |
||||
|
if (parent.kind === "constructor") { |
||||
|
return "constructors"; |
||||
|
} |
||||
|
kind = "methods"; |
||||
|
|
||||
|
} else { |
||||
|
kind = "functions"; |
||||
|
} |
||||
|
|
||||
|
// Detects prefix.
|
||||
|
var prefix = ""; |
||||
|
if (node.generator) { |
||||
|
prefix = "generator"; |
||||
|
} else if (node.async) { |
||||
|
prefix = "async"; |
||||
|
} else { |
||||
|
return kind; |
||||
|
} |
||||
|
return prefix + kind[0].toUpperCase() + kind.slice(1); |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
var options = context.options[0] || {}; |
||||
|
var allowed = options.allow || []; |
||||
|
|
||||
|
/** |
||||
|
* Reports a given function node if the node matches the following patterns. |
||||
|
* |
||||
|
* - Not allowed by options. |
||||
|
* - The body is empty. |
||||
|
* - The body doesn't have any comments. |
||||
|
* |
||||
|
* @param {ASTNode} node - A function node to report. This is one of |
||||
|
* an ArrowFunctionExpression, a FunctionDeclaration, or a |
||||
|
* FunctionExpression. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportIfEmpty(node) { |
||||
|
var kind = getKind(node); |
||||
|
|
||||
|
if (allowed.indexOf(kind) === -1 && |
||||
|
node.body.type === "BlockStatement" && |
||||
|
node.body.body.length === 0 && |
||||
|
context.getComments(node.body).trailing.length === 0 |
||||
|
) { |
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: node.body.loc.start, |
||||
|
message: "Unexpected empty " + SHOW_KIND[kind] + "." |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
ArrowFunctionExpression: reportIfEmpty, |
||||
|
FunctionDeclaration: reportIfEmpty, |
||||
|
FunctionExpression: reportIfEmpty |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = [ |
||||
|
{ |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
allow: { |
||||
|
type: "array", |
||||
|
items: {enum: ALLOW_OPTIONS}, |
||||
|
uniqueItems: true |
||||
|
} |
||||
|
}, |
||||
|
additionalProperties: false |
||||
|
} |
||||
|
]; |
@ -1,27 +0,0 @@ |
|||||
/** |
|
||||
* @fileoverview Rule to flag when label is not used for a loop or switch |
|
||||
* @author Ilya Volodin |
|
||||
*/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
// Rule Definition
|
|
||||
//------------------------------------------------------------------------------
|
|
||||
|
|
||||
module.exports = function(context) { |
|
||||
|
|
||||
return { |
|
||||
|
|
||||
"LabeledStatement": function(node) { |
|
||||
var type = node.body.type; |
|
||||
|
|
||||
if (type !== "ForStatement" && type !== "WhileStatement" && type !== "DoWhileStatement" && type !== "SwitchStatement" && type !== "ForInStatement" && type !== "ForOfStatement") { |
|
||||
context.report(node, "Unexpected label \"{{l}}\"", {l: node.label.name}); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
}; |
|
||||
|
|
||||
module.exports.schema = []; |
|
@ -1,26 +1,290 @@ |
|||||
/** |
/** |
||||
* @fileoverview Rule to flag use of eval() statement |
* @fileoverview Rule to flag use of eval() statement |
||||
* @author Nicholas C. Zakas |
* @author Nicholas C. Zakas |
||||
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
||||
* @copyright 2015 Mathias Schreck. All rights reserved. |
* @copyright 2015 Mathias Schreck. All rights reserved. |
||||
* @copyright 2013 Nicholas C. Zakas. All rights reserved. |
* @copyright 2013 Nicholas C. Zakas. All rights reserved. |
||||
*/ |
*/ |
||||
|
|
||||
"use strict"; |
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Helpers
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var candidatesOfGlobalObject = Object.freeze([ |
||||
|
"global", |
||||
|
"window" |
||||
|
]); |
||||
|
|
||||
|
/** |
||||
|
* Checks a given node is a Identifier node of the specified name. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @param {string} name - A name to check. |
||||
|
* @returns {boolean} `true` if the node is a Identifier node of the name. |
||||
|
*/ |
||||
|
function isIdentifier(node, name) { |
||||
|
return node.type === "Identifier" && node.name === name; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks a given node is a Literal node of the specified string value. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @param {string} name - A name to check. |
||||
|
* @returns {boolean} `true` if the node is a Literal node of the name. |
||||
|
*/ |
||||
|
function isConstant(node, name) { |
||||
|
switch (node.type) { |
||||
|
case "Literal": |
||||
|
return node.value === name; |
||||
|
|
||||
|
case "TemplateLiteral": |
||||
|
return ( |
||||
|
node.expressions.length === 0 && |
||||
|
node.quasis[0].value.cooked === name |
||||
|
); |
||||
|
|
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks a given node is a MemberExpression node which has the specified name's |
||||
|
* property. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to check. |
||||
|
* @param {string} name - A name to check. |
||||
|
* @returns {boolean} `true` if the node is a MemberExpression node which has |
||||
|
* the specified name's property |
||||
|
*/ |
||||
|
function isMember(node, name) { |
||||
|
return ( |
||||
|
node.type === "MemberExpression" && |
||||
|
(node.computed ? isConstant : isIdentifier)(node.property, name) |
||||
|
); |
||||
|
} |
||||
|
|
||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
module.exports = function(context) { |
module.exports = function(context) { |
||||
|
var allowIndirect = Boolean( |
||||
|
context.options[0] && |
||||
|
context.options[0].allowIndirect |
||||
|
); |
||||
|
var sourceCode = context.getSourceCode(); |
||||
|
var funcInfo = null; |
||||
|
|
||||
|
/** |
||||
|
* Pushs a variable scope (Program or Function) information to the stack. |
||||
|
* |
||||
|
* This is used in order to check whether or not `this` binding is a |
||||
|
* reference to the global object. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node of the scope. This is one of Program, |
||||
|
* FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function enterVarScope(node) { |
||||
|
var strict = context.getScope().isStrict; |
||||
|
|
||||
|
funcInfo = { |
||||
|
upper: funcInfo, |
||||
|
node: node, |
||||
|
strict: strict, |
||||
|
defaultThis: false, |
||||
|
initialized: strict |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Pops a variable scope from the stack. |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function exitVarScope() { |
||||
|
funcInfo = funcInfo.upper; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given node. |
||||
|
* |
||||
|
* `node` is `Identifier` or `MemberExpression`. |
||||
|
* The parent of `node` might be `CallExpression`. |
||||
|
* |
||||
|
* The location of the report is always `eval` `Identifier` (or possibly |
||||
|
* `Literal`). The type of the report is `CallExpression` if the parent is |
||||
|
* `CallExpression`. Otherwise, it's the given node type. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to report. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function report(node) { |
||||
|
var locationNode = node; |
||||
|
var parent = node.parent; |
||||
|
|
||||
|
if (node.type === "MemberExpression") { |
||||
|
locationNode = node.property; |
||||
|
} |
||||
|
if (parent.type === "CallExpression" && parent.callee === node) { |
||||
|
node = parent; |
||||
|
} |
||||
|
|
||||
|
context.report({ |
||||
|
node: node, |
||||
|
loc: locationNode.loc.start, |
||||
|
message: "eval can be harmful." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports accesses of `eval` via the global object. |
||||
|
* |
||||
|
* @param {escope.Scope} globalScope - The global scope. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportAccessingEvalViaGlobalObject(globalScope) { |
||||
|
for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { |
||||
|
var name = candidatesOfGlobalObject[i]; |
||||
|
var variable = astUtils.getVariableByName(globalScope, name); |
||||
|
if (!variable) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var references = variable.references; |
||||
|
for (var j = 0; j < references.length; ++j) { |
||||
|
var identifier = references[j].identifier; |
||||
|
var node = identifier.parent; |
||||
|
|
||||
|
// To detect code like `window.window.eval`.
|
||||
|
while (isMember(node, name)) { |
||||
|
node = node.parent; |
||||
|
} |
||||
|
|
||||
|
// Reports.
|
||||
|
if (isMember(node, "eval")) { |
||||
|
report(node); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports all accesses of `eval` (excludes direct calls to eval). |
||||
|
* |
||||
|
* @param {escope.Scope} globalScope - The global scope. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportAccessingEval(globalScope) { |
||||
|
var variable = astUtils.getVariableByName(globalScope, "eval"); |
||||
|
if (!variable) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var references = variable.references; |
||||
|
for (var i = 0; i < references.length; ++i) { |
||||
|
var reference = references[i]; |
||||
|
var id = reference.identifier; |
||||
|
|
||||
|
if (id.name === "eval" && !astUtils.isCallee(id)) { |
||||
|
// Is accessing to eval (excludes direct calls to eval)
|
||||
|
report(id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (allowIndirect) { |
||||
|
// Checks only direct calls to eval.
|
||||
|
// It's simple!
|
||||
|
return { |
||||
|
"CallExpression:exit": function(node) { |
||||
|
var callee = node.callee; |
||||
|
if (isIdentifier(callee, "eval")) { |
||||
|
report(callee); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
return { |
return { |
||||
"CallExpression": function(node) { |
"CallExpression:exit": function(node) { |
||||
if (node.callee.name === "eval") { |
var callee = node.callee; |
||||
context.report(node, "eval can be harmful."); |
if (isIdentifier(callee, "eval")) { |
||||
|
report(callee); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
"Program": function(node) { |
||||
|
var scope = context.getScope(), |
||||
|
features = context.parserOptions.ecmaFeatures || {}, |
||||
|
strict = |
||||
|
scope.isStrict || |
||||
|
node.sourceType === "module" || |
||||
|
(features.globalReturn && scope.childScopes[0].isStrict); |
||||
|
|
||||
|
funcInfo = { |
||||
|
upper: null, |
||||
|
node: node, |
||||
|
strict: strict, |
||||
|
defaultThis: true, |
||||
|
initialized: true |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
"Program:exit": function() { |
||||
|
var globalScope = context.getScope(); |
||||
|
|
||||
|
exitVarScope(); |
||||
|
reportAccessingEval(globalScope); |
||||
|
reportAccessingEvalViaGlobalObject(globalScope); |
||||
|
}, |
||||
|
|
||||
|
"FunctionDeclaration": enterVarScope, |
||||
|
"FunctionDeclaration:exit": exitVarScope, |
||||
|
"FunctionExpression": enterVarScope, |
||||
|
"FunctionExpression:exit": exitVarScope, |
||||
|
"ArrowFunctionExpression": enterVarScope, |
||||
|
"ArrowFunctionExpression:exit": exitVarScope, |
||||
|
|
||||
|
"ThisExpression": function(node) { |
||||
|
if (!isMember(node.parent, "eval")) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// `this.eval` is found.
|
||||
|
// Checks whether or not the value of `this` is the global object.
|
||||
|
if (!funcInfo.initialized) { |
||||
|
funcInfo.initialized = true; |
||||
|
funcInfo.defaultThis = astUtils.isDefaultThisBinding( |
||||
|
funcInfo.node, |
||||
|
sourceCode |
||||
|
); |
||||
|
} |
||||
|
if (!funcInfo.strict && funcInfo.defaultThis) { |
||||
|
// `this.eval` is possible built-in `eval`.
|
||||
|
report(node.parent); |
||||
} |
} |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
}; |
}; |
||||
|
|
||||
module.exports.schema = []; |
module.exports.schema = [ |
||||
|
{ |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"allowIndirect": {"type": "boolean"} |
||||
|
}, |
||||
|
"additionalProperties": false |
||||
|
} |
||||
|
]; |
||||
|
@ -0,0 +1,132 @@ |
|||||
|
/** |
||||
|
* @fileoverview Rule to disallow unnecessary labels |
||||
|
* @author Toru Nagashima |
||||
|
* @copyright 2016 Toru Nagashima. All rights reserved. |
||||
|
* See LICENSE file in root directory for full license. |
||||
|
*/ |
||||
|
|
||||
|
"use strict"; |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Requirements
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
var astUtils = require("../ast-utils"); |
||||
|
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
// Rule Definition
|
||||
|
//------------------------------------------------------------------------------
|
||||
|
|
||||
|
module.exports = function(context) { |
||||
|
var scopeInfo = null; |
||||
|
|
||||
|
/** |
||||
|
* Creates a new scope with a breakable statement. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to create. This is a BreakableStatement. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function enterBreakableStatement(node) { |
||||
|
scopeInfo = { |
||||
|
label: astUtils.getLabel(node), |
||||
|
breakable: true, |
||||
|
upper: scopeInfo |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Removes the top scope of the stack. |
||||
|
* |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function exitBreakableStatement() { |
||||
|
scopeInfo = scopeInfo.upper; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Creates a new scope with a labeled statement. |
||||
|
* |
||||
|
* This ignores it if the body is a breakable statement. |
||||
|
* In this case it's handled in the `enterBreakableStatement` function. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node to create. This is a LabeledStatement. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function enterLabeledStatement(node) { |
||||
|
if (!astUtils.isBreakableStatement(node.body)) { |
||||
|
scopeInfo = { |
||||
|
label: node.label.name, |
||||
|
breakable: false, |
||||
|
upper: scopeInfo |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Removes the top scope of the stack. |
||||
|
* |
||||
|
* This ignores it if the body is a breakable statement. |
||||
|
* In this case it's handled in the `exitBreakableStatement` function. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node. This is a LabeledStatement. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function exitLabeledStatement(node) { |
||||
|
if (!astUtils.isBreakableStatement(node.body)) { |
||||
|
scopeInfo = scopeInfo.upper; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reports a given control node if it's unnecessary. |
||||
|
* |
||||
|
* @param {ASTNode} node - A node. This is a BreakStatement or a |
||||
|
* ContinueStatement. |
||||
|
* @returns {void} |
||||
|
*/ |
||||
|
function reportIfUnnecessary(node) { |
||||
|
if (!node.label) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var labelNode = node.label; |
||||
|
var label = labelNode.name; |
||||
|
var info = scopeInfo; |
||||
|
|
||||
|
while (info) { |
||||
|
if (info.breakable || info.label === label) { |
||||
|
if (info.breakable && info.label === label) { |
||||
|
context.report({ |
||||
|
node: labelNode, |
||||
|
message: "This label '{{name}}' is unnecessary.", |
||||
|
data: labelNode |
||||
|
}); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
info = info.upper; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
"WhileStatement": enterBreakableStatement, |
||||
|
"WhileStatement:exit": exitBreakableStatement, |
||||
|
"DoWhileStatement": enterBreakableStatement, |
||||
|
"DoWhileStatement:exit": exitBreakableStatement, |
||||
|
"ForStatement": enterBreakableStatement, |
||||
|
"ForStatement:exit": exitBreakableStatement, |
||||
|
"ForInStatement": enterBreakableStatement, |
||||
|
"ForInStatement:exit": exitBreakableStatement, |
||||
|
"ForOfStatement": enterBreakableStatement, |
||||
|
"ForOfStatement:exit": exitBreakableStatement, |
||||
|
"SwitchStatement": enterBreakableStatement, |
||||
|
"SwitchStatement:exit": exitBreakableStatement, |
||||
|
"LabeledStatement": enterLabeledStatement, |
||||
|
"LabeledStatement:exit": exitLabeledStatement, |
||||
|
"BreakStatement": reportIfUnnecessary, |
||||
|
"ContinueStatement": reportIfUnnecessary |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
module.exports.schema = []; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue