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 |
|||
* @author Nicholas C. Zakas |
|||
* @copyright 2015 Toru Nagashima. All rights reserved. |
|||
* @copyright 2015 Mathias Schreck. All rights reserved. |
|||
* @copyright 2013 Nicholas C. Zakas. All rights reserved. |
|||
*/ |
|||
|
|||
"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
|
|||
//------------------------------------------------------------------------------
|
|||
|
|||
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": function(node) { |
|||
if (node.callee.name === "eval") { |
|||
context.report(node, "eval can be harmful."); |
|||
"CallExpression:exit": function(node) { |
|||
var callee = node.callee; |
|||
if (isIdentifier(callee, "eval")) { |
|||
report(callee); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
return { |
|||
"CallExpression:exit": function(node) { |
|||
var callee = node.callee; |
|||
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