2017-05-28 00:38:50 +02:00
"use strict" ;
/ * *
* @ license
* Copyright 2013 Palantir Technologies , Inc .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
var tslib _1 = require ( "tslib" ) ;
var ts = require ( "typescript" ) ;
var Lint = require ( "../index" ) ;
var utils = require ( "tsutils" ) ;
var OPTION _DESTRUCTURING _ALL = "all" ;
var OPTION _DESTRUCTURING _ANY = "any" ;
2017-10-14 18:40:54 +02:00
var Rule = /** @class */ ( function ( _super ) {
2017-05-28 00:38:50 +02:00
tslib _1 . _ _extends ( Rule , _super ) ;
function Rule ( ) {
return _super !== null && _super . apply ( this , arguments ) || this ;
}
/* tslint:enable:object-literal-sort-keys */
Rule . FAILURE _STRING _FACTORY = function ( identifier , blockScoped ) {
return "Identifier '" + identifier + "' is never reassigned; use 'const' instead of '" + ( blockScoped ? "let" : "var" ) + "'." ;
} ;
Rule . prototype . apply = function ( sourceFile ) {
var options = {
2017-08-14 05:01:11 +02:00
destructuringAll : this . ruleArguments . length !== 0 && this . ruleArguments [ 0 ] . destructuring === OPTION _DESTRUCTURING _ALL ,
2017-05-28 00:38:50 +02:00
} ;
var preferConstWalker = new PreferConstWalker ( sourceFile , this . ruleName , options ) ;
return this . applyWithWalker ( preferConstWalker ) ;
} ;
2017-08-14 05:01:11 +02:00
/* tslint:disable:object-literal-sort-keys */
Rule . metadata = {
ruleName : "prefer-const" ,
description : "Requires that variable declarations use `const` instead of `let` and `var` if possible." ,
descriptionDetails : ( _a = [ "\n If a variable is only assigned to once when it is declared, it should be declared using 'const'" ] , _a . raw = [ "\n If a variable is only assigned to once when it is declared, it should be declared using 'const'" ] , Lint . Utils . dedent ( _a ) ) ,
hasFix : true ,
optionsDescription : ( _b = [ "\n An optional object containing the property \"destructuring\" with two possible values:\n\n * \"" , "\" (default) - If any variable in destructuring can be const, this rule warns for those variables.\n * \"" , "\" - Only warns if all variables in destructuring can be const." ] , _b . raw = [ "\n An optional object containing the property \"destructuring\" with two possible values:\n\n * \"" , "\" (default) - If any variable in destructuring can be const, this rule warns for those variables.\n * \"" , "\" - Only warns if all variables in destructuring can be const." ] , Lint . Utils . dedent ( _b , OPTION _DESTRUCTURING _ANY , OPTION _DESTRUCTURING _ALL ) ) ,
options : {
type : "object" ,
properties : {
destructuring : {
type : "string" ,
enum : [ OPTION _DESTRUCTURING _ALL , OPTION _DESTRUCTURING _ANY ] ,
} ,
2017-05-28 00:38:50 +02:00
} ,
} ,
2017-08-14 05:01:11 +02:00
optionExamples : [
true ,
[ true , { destructuring : OPTION _DESTRUCTURING _ALL } ] ,
] ,
type : "maintainability" ,
typescriptOnly : false ,
} ;
return Rule ;
} ( Lint . Rules . AbstractRule ) ) ;
2017-05-28 00:38:50 +02:00
exports . Rule = Rule ;
2017-10-14 18:40:54 +02:00
var Scope = /** @class */ ( function ( ) {
2017-05-28 00:38:50 +02:00
function Scope ( functionScope ) {
this . variables = new Map ( ) ;
this . reassigned = new Set ( ) ;
// if no functionScope is provided we are in the process of creating a new function scope, which for consistency links to itself
this . functionScope = functionScope === undefined ? this : functionScope ;
}
Scope . prototype . addVariable = function ( identifier , declarationInfo , destructuringInfo ) {
// block scoped variables go to the block scope, function scoped variables to the containing function scope
var scope = declarationInfo . isBlockScoped ? this : this . functionScope ;
scope . variables . set ( identifier . text , {
declarationInfo : declarationInfo ,
destructuringInfo : destructuringInfo ,
identifier : identifier ,
reassigned : false ,
} ) ;
} ;
return Scope ;
} ( ) ) ;
2017-10-14 18:40:54 +02:00
var PreferConstWalker = /** @class */ ( function ( _super ) {
2017-05-28 00:38:50 +02:00
tslib _1 . _ _extends ( PreferConstWalker , _super ) ;
function PreferConstWalker ( ) {
return _super !== null && _super . apply ( this , arguments ) || this ;
}
PreferConstWalker . prototype . walk = function ( sourceFile ) {
var _this = this ;
// don't check anything on declaration files
if ( sourceFile . isDeclarationFile ) {
return ;
}
this . scope = new Scope ( ) ;
var cb = function ( node ) {
var savedScope = _this . scope ;
var boundary = utils . isScopeBoundary ( node ) ;
if ( boundary !== 0 /* None */ ) {
if ( boundary === 1 /* Function */ ) {
if ( node . kind === ts . SyntaxKind . ModuleDeclaration && utils . hasModifier ( node . modifiers , ts . SyntaxKind . DeclareKeyword ) ) {
// don't check ambient namespaces
return ;
}
_this . scope = new Scope ( ) ;
if ( utils . isFunctionDeclaration ( node ) ||
utils . isMethodDeclaration ( node ) ||
utils . isFunctionExpression ( node ) ||
utils . isArrowFunction ( node ) ||
utils . isConstructorDeclaration ( node ) ) {
// special handling for function parameters
// each parameter initializer can only reassign preceding parameters of variables of the containing scope
if ( node . body !== undefined ) {
for ( var _i = 0 , _a = node . parameters ; _i < _a . length ; _i ++ ) {
var param = _a [ _i ] ;
cb ( param ) ;
_this . settle ( savedScope ) ;
}
cb ( node . body ) ;
_this . onScopeEnd ( savedScope ) ;
}
_this . scope = savedScope ;
return ;
}
}
else {
_this . scope = new Scope ( _this . scope . functionScope ) ;
if ( ( utils . isForInStatement ( node ) || utils . isForOfStatement ( node ) ) &&
node . initializer . kind !== ts . SyntaxKind . VariableDeclarationList ) {
_this . handleExpression ( node . initializer ) ;
}
}
}
if ( node . kind === ts . SyntaxKind . VariableDeclarationList ) {
_this . handleVariableDeclaration ( node ) ;
}
else if ( node . kind === ts . SyntaxKind . CatchClause ) {
2017-10-14 18:40:54 +02:00
if ( node . variableDeclaration !== undefined ) {
_this . handleBindingName ( node . variableDeclaration . name , {
canBeConst : false ,
isBlockScoped : true ,
} ) ;
}
2017-05-28 00:38:50 +02:00
}
else if ( node . kind === ts . SyntaxKind . Parameter ) {
_this . handleBindingName ( node . name , {
canBeConst : false ,
isBlockScoped : true ,
} ) ;
}
else if ( utils . isPostfixUnaryExpression ( node ) ||
utils . isPrefixUnaryExpression ( node ) &&
( node . operator === ts . SyntaxKind . PlusPlusToken || node . operator === ts . SyntaxKind . MinusMinusToken ) ) {
if ( utils . isIdentifier ( node . operand ) ) {
_this . scope . reassigned . add ( node . operand . text ) ;
}
}
else if ( utils . isBinaryExpression ( node ) && utils . isAssignmentKind ( node . operatorToken . kind ) ) {
_this . handleExpression ( node . left ) ;
}
if ( boundary !== 0 /* None */ ) {
ts . forEachChild ( node , cb ) ;
_this . onScopeEnd ( savedScope ) ;
_this . scope = savedScope ;
}
else {
return ts . forEachChild ( node , cb ) ;
}
} ;
if ( ts . isExternalModule ( sourceFile ) ) {
ts . forEachChild ( sourceFile , cb ) ;
this . onScopeEnd ( ) ;
}
else {
return ts . forEachChild ( sourceFile , cb ) ;
}
} ;
PreferConstWalker . prototype . handleExpression = function ( node ) {
switch ( node . kind ) {
case ts . SyntaxKind . Identifier :
this . scope . reassigned . add ( node . text ) ;
break ;
case ts . SyntaxKind . ParenthesizedExpression :
this . handleExpression ( node . expression ) ;
break ;
case ts . SyntaxKind . ArrayLiteralExpression :
for ( var _i = 0 , _a = node . elements ; _i < _a . length ; _i ++ ) {
var element = _a [ _i ] ;
if ( element . kind === ts . SyntaxKind . SpreadElement ) {
this . handleExpression ( element . expression ) ;
}
else {
this . handleExpression ( element ) ;
}
}
break ;
case ts . SyntaxKind . ObjectLiteralExpression :
for ( var _b = 0 , _c = node . properties ; _b < _c . length ; _b ++ ) {
var property = _c [ _b ] ;
switch ( property . kind ) {
case ts . SyntaxKind . ShorthandPropertyAssignment :
this . scope . reassigned . add ( property . name . text ) ;
break ;
case ts . SyntaxKind . SpreadAssignment :
if ( property . name !== undefined ) {
this . scope . reassigned . add ( property . name . text ) ;
}
else {
// handle `...(variable)`
this . handleExpression ( property . expression ) ;
}
break ;
default :
this . handleExpression ( property . initializer ) ;
}
}
}
} ;
PreferConstWalker . prototype . handleBindingName = function ( name , declarationInfo ) {
var _this = this ;
if ( name . kind === ts . SyntaxKind . Identifier ) {
this . scope . addVariable ( name , declarationInfo ) ;
}
else {
var destructuringInfo _1 = {
reassignedSiblings : false ,
} ;
utils . forEachDestructuringIdentifier ( name , function ( declaration ) { return _this . scope . addVariable ( declaration . name , declarationInfo , destructuringInfo _1 ) ; } ) ;
}
} ;
PreferConstWalker . prototype . handleVariableDeclaration = function ( declarationList ) {
var declarationInfo ;
var kind = utils . getVariableDeclarationKind ( declarationList ) ;
if ( kind === 2 /* Const */ ||
utils . hasModifier ( declarationList . parent . modifiers , ts . SyntaxKind . ExportKeyword , ts . SyntaxKind . DeclareKeyword ) ) {
declarationInfo = {
canBeConst : false ,
isBlockScoped : kind !== 0 /* Var */ ,
} ;
}
else {
declarationInfo = {
allInitialized : declarationList . parent . kind === ts . SyntaxKind . ForOfStatement ||
declarationList . parent . kind === ts . SyntaxKind . ForInStatement ||
declarationList . declarations . every ( function ( declaration ) { return declaration . initializer !== undefined ; } ) ,
canBeConst : true ,
declarationList : declarationList ,
isBlockScoped : kind === 1 /* Let */ ,
2017-08-14 05:01:11 +02:00
isForLoop : declarationList . parent . kind === ts . SyntaxKind . ForStatement ||
declarationList . parent . kind === ts . SyntaxKind . ForOfStatement ,
2017-05-28 00:38:50 +02:00
reassignedSiblings : false ,
} ;
}
for ( var _i = 0 , _a = declarationList . declarations ; _i < _a . length ; _i ++ ) {
var declaration = _a [ _i ] ;
this . handleBindingName ( declaration . name , declarationInfo ) ;
}
} ;
PreferConstWalker . prototype . settle = function ( parent ) {
var _a = this . scope , variables = _a . variables , reassigned = _a . reassigned ;
reassigned . forEach ( function ( name ) {
var variableInfo = variables . get ( name ) ;
if ( variableInfo !== undefined ) {
if ( variableInfo . declarationInfo . canBeConst ) {
variableInfo . reassigned = true ;
variableInfo . declarationInfo . reassignedSiblings = true ;
if ( variableInfo . destructuringInfo !== undefined ) {
variableInfo . destructuringInfo . reassignedSiblings = true ;
}
}
}
else if ( parent !== undefined ) {
// if the reassigned variable was not declared in this scope we defer to the parent scope
parent . reassigned . add ( name ) ;
}
} ) ;
reassigned . clear ( ) ;
} ;
PreferConstWalker . prototype . onScopeEnd = function ( parent ) {
var _this = this ;
this . settle ( parent ) ;
var appliedFixes = new Set ( ) ;
this . scope . variables . forEach ( function ( info , name ) {
if ( info . declarationInfo . canBeConst &&
! info . reassigned &&
// don't add failures for reassigned variables in for loop initializer
! ( info . declarationInfo . reassignedSiblings && info . declarationInfo . isForLoop ) &&
// if {destructuring: "all"} is set, only add a failure if all variables in a destructuring assignment can be const
( ! _this . options . destructuringAll ||
info . destructuringInfo === undefined ||
! info . destructuringInfo . reassignedSiblings ) ) {
var fix = void 0 ;
// only apply fixes if the VariableDeclarationList has no reassigned variables
// and the variable is block scoped aka `let` and initialized
if ( info . declarationInfo . allInitialized &&
! info . declarationInfo . reassignedSiblings &&
info . declarationInfo . isBlockScoped &&
! appliedFixes . has ( info . declarationInfo . declarationList ) ) {
fix = new Lint . Replacement ( info . declarationInfo . declarationList . getStart ( _this . sourceFile ) , 3 , "const" ) ;
// add only one fixer per VariableDeclarationList
appliedFixes . add ( info . declarationInfo . declarationList ) ;
}
_this . addFailureAtNode ( info . identifier , Rule . FAILURE _STRING _FACTORY ( name , info . declarationInfo . isBlockScoped ) , fix ) ;
}
} ) ;
} ;
return PreferConstWalker ;
} ( Lint . AbstractWalker ) ) ;
var _a , _b ;