Permalink
Jump to Line
Branch:
master
Switch branches/tags
dollar-ignores
feature/new-rule-require-new-lines-in-array
gh-pages
issues/1242
issues/1271-related
master
mdevils/cst
Nothing to show
Nothing to show
Fetching contributors…
![]()
Cannot retrieve contributors at this time
| /** | |
| * Requires `var` declaration to be on the top of an enclosing scope | |
| * | |
| * Types: `Boolean` | |
| * | |
| * Values: | |
| * | |
| * - `true` specifies that `var` declarations must occur the top of a function scope. | |
| * | |
| * #### Example | |
| * | |
| * ```js | |
| * "requireVarDeclFirst": true | |
| * ``` | |
| * | |
| * ##### Valid for mode `true` | |
| * | |
| * ```js | |
| * var x = 1, | |
| * y = 2; | |
| * ``` | |
| * ```js | |
| * 'use strict;' | |
| * var x = 1, | |
| * y = 2; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * var y = 2; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * // comments | |
| * var y = 2; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * // comments | |
| * // comments 2 | |
| * var y = 2; | |
| * ``` | |
| * ```js | |
| * const a = 1; | |
| * const b = 2; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * function y() {var z;}; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * var y = function () {var z;}; | |
| * ``` | |
| * ```js | |
| * var w = 1; | |
| * function x() { | |
| * var y; | |
| * // comments | |
| * // comments 2 | |
| * var z; | |
| * }; | |
| * ``` | |
| * ```js | |
| * var w = 1; | |
| * function x() { | |
| * "use strict"; | |
| * var y; | |
| * }; | |
| * ``` | |
| * ```js | |
| * var x = 1; | |
| * var y; | |
| * for (y = 0; y < 10; y++) {}; | |
| * ``` | |
| * | |
| * ##### Invalid | |
| * | |
| * ```js | |
| * var x; | |
| * x = 1; | |
| * var y = 2; | |
| * ``` | |
| * ```js | |
| * var w = 1; | |
| * function x() {var y;}; | |
| * var z = 2; | |
| * ``` | |
| * ```js | |
| * var w = 1; | |
| * function x() { | |
| * var y; | |
| * y = 2; | |
| * var z; | |
| * }; | |
| * ``` | |
| * ```js | |
| * var a; | |
| * for(var count=0;count < 10;count++){} | |
| * ``` | |
| * ```js | |
| * var x; | |
| * for(var count=0;count < 10;count++){ | |
| * var y; | |
| * } | |
| * ``` | |
| * | |
| */ | |
| var assert = require('assert'); | |
| function getVariableScope(node) { | |
| while (node.type !== 'Program' && | |
| node.type !== 'FunctionDeclaration' && | |
| node.type !== 'FunctionExpression') { | |
| node = node.parentNode; | |
| } | |
| return node; | |
| } | |
| function getOffsetForBlockStatement(enclosingScope, varDecl, commentTokens) { | |
| var offset = 0; | |
| var parentNode = varDecl.parentNode; | |
| if (enclosingScope.type !== 'Program' && parentNode.type === 'BlockStatement') { | |
| offset += 1; | |
| offset += getCommentOffsetBetweenNodes(parentNode, varDecl, commentTokens); | |
| } | |
| return offset; | |
| } | |
| function getUseStrictDeclFirst(enclosingScope) { | |
| var firstNode; | |
| if (enclosingScope.type === 'Program') { | |
| firstNode = enclosingScope.body[0]; | |
| } else { | |
| firstNode = enclosingScope.body.body[0]; | |
| } | |
| if (firstNode.type === 'ExpressionStatement' && | |
| firstNode.hasOwnProperty('expression') === true && | |
| firstNode.expression.hasOwnProperty('value') === true && | |
| firstNode.expression.value === 'use strict') { | |
| return firstNode; | |
| } | |
| return null; | |
| } | |
| function isFirstVarDeclInScope(enclosingScope, varDecl, whitespaceOffsetBeforeVarDecl, commentTokens) { | |
| var adjustedVarDeclStart = varDecl.range[0]; | |
| var adjustedScopeStart = enclosingScope.range[0]; | |
| if (enclosingScope.type !== 'Program') { | |
| // For function declaration and function expression scope use the top block statement as start | |
| // This removes the requirement to offset the function declaration or expression related tokens | |
| adjustedScopeStart = enclosingScope.body.range[0]; | |
| // If enclosing scope node type is Program the range start ignores all comments and whitespace before the | |
| // variable declaration | |
| adjustedVarDeclStart -= whitespaceOffsetBeforeVarDecl; | |
| } | |
| adjustedVarDeclStart -= getOffsetForBlockStatement(enclosingScope, varDecl, commentTokens); | |
| if (adjustedVarDeclStart === adjustedScopeStart) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| function getCommentOffsetBetweenNodes(previousNode, currentNode, commentTokens) { | |
| var count; | |
| var comment; | |
| var commentLength = 0; | |
| for (count = 0; count < commentTokens.length; count++) { | |
| comment = commentTokens[count]; | |
| if (comment.range[0] >= currentNode.range[1]) { | |
| // Stop processing comments that are occurred after current node | |
| break; | |
| } | |
| if (comment.range[0] > currentNode.range[0] && | |
| comment.range[1] < currentNode.range[1]) { | |
| // Stop processing comments that are within multiple declarators in a single variable declaration | |
| break; | |
| } | |
| if (previousNode.range[0] >= comment.range[1]) { | |
| // Skip comments that occurred before the previous node | |
| continue; | |
| } | |
| commentLength += comment.range[1] - comment.range[0] + comment.whitespaceBefore.length; | |
| } | |
| return commentLength; | |
| } | |
| function isPreviousNodeAVarDecl(previousNode, varDecl, whitespaceOffsetBeforeVarDecl, commentTokens) { | |
| var offsetForComments; | |
| if (varDecl.range[0] === previousNode.range[1]) { | |
| return true; | |
| } | |
| offsetForComments = getCommentOffsetBetweenNodes(previousNode, varDecl, commentTokens); | |
| if (varDecl.range[0] - whitespaceOffsetBeforeVarDecl - offsetForComments === previousNode.range[1]) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| module.exports = function() {}; | |
| module.exports.prototype = { | |
| configure: function(options) { | |
| assert( | |
| options === true, | |
| this.getOptionName() + ' option requires a true value' | |
| ); | |
| }, | |
| getOptionName: function() { | |
| return 'requireVarDeclFirst'; | |
| }, | |
| check: function(file, errors) { | |
| var scopesFoundInFile = {}; | |
| var commentTokens = []; | |
| file.iterateTokensByType(['Line', 'Block'], function(commentToken) { | |
| commentTokens.push(commentToken); | |
| }); | |
| file.iterateNodesByType(['VariableDeclaration'], function(varDecl) { | |
| var enclosingScope; | |
| var scopeContents; | |
| var previousNode; | |
| var useStrictDirective; | |
| var isVarDeclFirst = false; | |
| var whitespaceOffsetBeforeVarDecl = file.getFirstNodeToken(varDecl).whitespaceBefore.length; | |
| enclosingScope = getVariableScope(varDecl.parentNode); | |
| if (!scopesFoundInFile.hasOwnProperty(enclosingScope.range[0])) { | |
| scopesFoundInFile[enclosingScope.range[0]] = { hasNonVarDecl: false, varDecl: [] }; | |
| // placing the handling 'use strict' declared as the first statement of scope here to improve | |
| // performance to run once per scope discovered in file | |
| useStrictDirective = getUseStrictDeclFirst(enclosingScope); | |
| if (useStrictDirective !== null) { | |
| // Special case to make varDecl stack contain the use strict as first node | |
| // this reduces the complexity of the isFirstVarDecInScope and reuses | |
| // isPreviousNodeAVarDecl to handle this special scenario | |
| scopesFoundInFile[enclosingScope.range[0]].varDecl.push(useStrictDirective); | |
| } | |
| } | |
| scopeContents = scopesFoundInFile[enclosingScope.range[0]]; | |
| if (scopeContents.varDecl.length === 0) { | |
| isVarDeclFirst = isFirstVarDeclInScope( | |
| enclosingScope, varDecl, whitespaceOffsetBeforeVarDecl, commentTokens, file); | |
| } else { | |
| previousNode = scopeContents.varDecl[scopeContents.varDecl.length - 1]; | |
| if (!scopeContents.hasNonVarDecl) { | |
| isVarDeclFirst = isPreviousNodeAVarDecl( | |
| previousNode, varDecl, whitespaceOffsetBeforeVarDecl, commentTokens); | |
| } | |
| } | |
| scopeContents.varDecl.push(varDecl); | |
| if (!isVarDeclFirst) { | |
| scopeContents.hasNonVarDecl = true; | |
| errors.add('Variable declarations must be the first statements of a function scope.', | |
| varDecl.loc.start.line, varDecl.loc.start.column); | |
| } | |
| }); | |
| } | |
| }; |