2017-05-28 00:38:50 +02:00
"use strict" ;
/ * *
* @ license
* Copyright 2014 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 utils = require ( "tsutils" ) ;
var ts = require ( "typescript" ) ;
var Lint = require ( "../index" ) ;
var OPTION _CHECK _PARAMETERS = "check-parameters" ;
var OPTION _IGNORE _PATTERN = "ignore-pattern" ;
var Rule = ( function ( _super ) {
tslib _1 . _ _extends ( Rule , _super ) ;
function Rule ( ) {
return _super !== null && _super . apply ( this , arguments ) || this ;
}
/* tslint:enable:object-literal-sort-keys */
Rule . prototype . applyWithProgram = function ( sourceFile , program ) {
var _this = this ;
var x = program . getCompilerOptions ( ) ;
2017-08-14 05:01:11 +02:00
if ( x . noUnusedLocals && x . noUnusedParameters ) {
2017-05-28 00:38:50 +02:00
console . warn ( "WARNING: 'no-unused-variable' lint rule does not need to be set if " +
"the 'no-unused-locals' and 'no-unused-parameters' compiler options are enabled." ) ;
}
return this . applyWithFunction ( sourceFile , function ( ctx ) { return walk ( ctx , program , parseOptions ( _this . ruleArguments ) ) ; } ) ;
} ;
2017-08-14 05:01:11 +02:00
/* tslint:disable:object-literal-sort-keys */
Rule . metadata = {
ruleName : "no-unused-variable" ,
description : ( _a = [ "Disallows unused imports, variables, functions and\n private class members. Similar to tsc's --noUnusedParameters and --noUnusedLocals\n options, but does not interrupt code compilation." ] , _a . raw = [ "Disallows unused imports, variables, functions and\n private class members. Similar to tsc's --noUnusedParameters and --noUnusedLocals\n options, but does not interrupt code compilation." ] , Lint . Utils . dedent ( _a ) ) ,
hasFix : true ,
optionsDescription : ( _b = [ "\n Three optional arguments may be optionally provided:\n\n * `\"check-parameters\"` disallows unused function and constructor parameters.\n * NOTE: this option is experimental and does not work with classes\n that use abstract method declarations, among other things.\n * `{\"ignore-pattern\": \"pattern\"}` where pattern is a case-sensitive regexp.\n Variable names that match the pattern will be ignored." ] , _b . raw = [ "\n Three optional arguments may be optionally provided:\n\n * \\`\"check-parameters\"\\` disallows unused function and constructor parameters.\n * NOTE: this option is experimental and does not work with classes\n that use abstract method declarations, among other things.\n * \\`{\"ignore-pattern\": \"pattern\"}\\` where pattern is a case-sensitive regexp.\n Variable names that match the pattern will be ignored." ] , Lint . Utils . dedent ( _b ) ) ,
options : {
type : "array" ,
items : {
oneOf : [
{
type : "string" ,
enum : [ "check-parameters" ] ,
} ,
{
type : "object" ,
properties : {
"ignore-pattern" : { type : "string" } ,
} ,
additionalProperties : false ,
2017-05-28 00:38:50 +02:00
} ,
2017-08-14 05:01:11 +02:00
] ,
} ,
minLength : 0 ,
maxLength : 3 ,
2017-05-28 00:38:50 +02:00
} ,
2017-08-14 05:01:11 +02:00
optionExamples : [ true , [ true , { "ignore-pattern" : "^_" } ] ] ,
type : "functionality" ,
typescriptOnly : true ,
requiresTypeInfo : true ,
} ;
return Rule ;
} ( Lint . Rules . TypedRule ) ) ;
2017-05-28 00:38:50 +02:00
exports . Rule = Rule ;
function parseOptions ( options ) {
var checkParameters = options . indexOf ( OPTION _CHECK _PARAMETERS ) !== - 1 ;
var ignorePattern ;
for ( var _i = 0 , options _1 = options ; _i < options _1 . length ; _i ++ ) {
var o = options _1 [ _i ] ;
if ( typeof o === "object" ) {
// tslint:disable-next-line no-unsafe-any
var ignore = o [ OPTION _IGNORE _PATTERN ] ;
if ( ignore != null ) {
ignorePattern = new RegExp ( ignore ) ;
break ;
}
}
}
return { checkParameters : checkParameters , ignorePattern : ignorePattern } ;
}
function walk ( ctx , program , _a ) {
var checkParameters = _a . checkParameters , ignorePattern = _a . ignorePattern ;
var sourceFile = ctx . sourceFile ;
var unusedCheckedProgram = getUnusedCheckedProgram ( program , checkParameters ) ;
var diagnostics = ts . getPreEmitDiagnostics ( unusedCheckedProgram , sourceFile ) ;
var checker = unusedCheckedProgram . getTypeChecker ( ) ; // Doesn't matter which program is used for this.
2017-08-14 05:01:11 +02:00
var declaration = program . getCompilerOptions ( ) . declaration ;
2017-05-28 00:38:50 +02:00
// If all specifiers in an import are unused, we elide the entire import.
var importSpecifierFailures = new Map ( ) ;
for ( var _i = 0 , diagnostics _1 = diagnostics ; _i < diagnostics _1 . length ; _i ++ ) {
var diag = diagnostics _1 [ _i ] ;
2017-08-14 05:01:11 +02:00
if ( diag . start === undefined ) {
continue ;
}
2017-05-28 00:38:50 +02:00
var kind = getUnusedDiagnostic ( diag ) ;
if ( kind === undefined ) {
continue ;
}
var failure = ts . flattenDiagnosticMessageText ( diag . messageText , "\n" ) ;
if ( kind === 0 /* VARIABLE_OR_PARAMETER */ ) {
var importName = findImport ( diag . start , sourceFile ) ;
if ( importName !== undefined ) {
2017-08-14 05:01:11 +02:00
if ( declaration && isImportUsed ( importName , sourceFile , checker ) ) {
2017-05-28 00:38:50 +02:00
continue ;
}
if ( importSpecifierFailures . has ( importName ) ) {
throw new Error ( "Should not get 2 errors for the same import." ) ;
}
importSpecifierFailures . set ( importName , failure ) ;
continue ;
}
}
if ( ignorePattern !== undefined ) {
var varName = /'(.*)'/ . exec ( failure ) [ 1 ] ;
if ( ignorePattern . test ( varName ) ) {
continue ;
}
}
ctx . addFailureAt ( diag . start , diag . length , failure ) ;
}
if ( importSpecifierFailures . size !== 0 ) {
addImportSpecifierFailures ( ctx , importSpecifierFailures , sourceFile ) ;
}
}
/ * *
* Handle import - specifier failures separately .
* - If all of the import specifiers in an import are unused , add a combined failure for them all .
* - Unused imports are fixable .
* /
function addImportSpecifierFailures ( ctx , failures , sourceFile ) {
forEachImport ( sourceFile , function ( importNode ) {
if ( importNode . kind === ts . SyntaxKind . ImportEqualsDeclaration ) {
tryRemoveAll ( importNode . name ) ;
return ;
}
if ( importNode . importClause === undefined ) {
// Error node
return ;
}
var _a = importNode . importClause , defaultName = _a . name , namedBindings = _a . namedBindings ;
if ( namedBindings !== undefined && namedBindings . kind === ts . SyntaxKind . NamespaceImport ) {
tryRemoveAll ( namedBindings . name ) ;
return ;
}
var allNamedBindingsAreFailures = namedBindings === undefined || namedBindings . elements . every ( function ( e ) { return failures . has ( e . name ) ; } ) ;
if ( namedBindings !== undefined && allNamedBindingsAreFailures ) {
for ( var _i = 0 , _b = namedBindings . elements ; _i < _b . length ; _i ++ ) {
var e = _b [ _i ] ;
failures . delete ( e . name ) ;
}
}
if ( ( defaultName === undefined || failures . has ( defaultName ) ) && allNamedBindingsAreFailures ) {
if ( defaultName !== undefined ) {
failures . delete ( defaultName ) ;
}
removeAll ( importNode , "All imports are unused." ) ;
return ;
}
if ( defaultName !== undefined ) {
var failure = tryDelete ( defaultName ) ;
if ( failure !== undefined ) {
var start = defaultName . getStart ( ) ;
var end = namedBindings !== undefined ? namedBindings . getStart ( ) : importNode . moduleSpecifier . getStart ( ) ;
var fix = Lint . Replacement . deleteFromTo ( start , end ) ;
ctx . addFailureAtNode ( defaultName , failure , fix ) ;
}
}
if ( namedBindings !== undefined ) {
if ( allNamedBindingsAreFailures ) {
var start = defaultName !== undefined ? defaultName . getEnd ( ) : namedBindings . getStart ( ) ;
var fix = Lint . Replacement . deleteFromTo ( start , namedBindings . getEnd ( ) ) ;
var failure = "All named bindings are unused." ;
ctx . addFailureAtNode ( namedBindings , failure , fix ) ;
}
else {
var elements = namedBindings . elements ;
for ( var i = 0 ; i < elements . length ; i ++ ) {
var element = elements [ i ] ;
var failure = tryDelete ( element . name ) ;
if ( failure === undefined ) {
continue ;
}
var prevElement = elements [ i - 1 ] ;
var nextElement = elements [ i + 1 ] ;
var start = prevElement !== undefined ? prevElement . getEnd ( ) : element . getStart ( ) ;
var end = nextElement !== undefined && prevElement == undefined ? nextElement . getStart ( ) : element . getEnd ( ) ;
var fix = Lint . Replacement . deleteFromTo ( start , end ) ;
ctx . addFailureAtNode ( element . name , failure , fix ) ;
}
}
}
function tryRemoveAll ( name ) {
var failure = tryDelete ( name ) ;
if ( failure !== undefined ) {
removeAll ( name , failure ) ;
}
}
function removeAll ( errorNode , failure ) {
2017-08-14 05:01:11 +02:00
var start = importNode . getStart ( ) ;
var end = importNode . getEnd ( ) ;
if ( isEntireLine ( start , end ) ) {
end = getNextLineStart ( end ) ;
}
var fix = Lint . Replacement . deleteFromTo ( start , end ) ;
2017-05-28 00:38:50 +02:00
ctx . addFailureAtNode ( errorNode , failure , fix ) ;
}
2017-08-14 05:01:11 +02:00
function isEntireLine ( start , end ) {
return ctx . sourceFile . getLineAndCharacterOfPosition ( start ) . character === 0 &&
ctx . sourceFile . getLineEndOfPosition ( end ) === end ;
}
function getNextLineStart ( position ) {
var nextLine = ctx . sourceFile . getLineAndCharacterOfPosition ( position ) . line + 1 ;
var lineStarts = ctx . sourceFile . getLineStarts ( ) ;
if ( nextLine < lineStarts . length ) {
return lineStarts [ nextLine ] ;
}
else {
return position ;
}
}
2017-05-28 00:38:50 +02:00
} ) ;
if ( failures . size !== 0 ) {
throw new Error ( "Should have revisited all import specifier failures." ) ;
}
function tryDelete ( name ) {
var failure = failures . get ( name ) ;
if ( failure !== undefined ) {
failures . delete ( name ) ;
return failure ;
}
return undefined ;
}
}
/ * *
* Ignore this import if it ' s used as an implicit type somewhere .
* Workround for https : //github.com/Microsoft/TypeScript/issues/9944
* /
function isImportUsed ( importSpecifier , sourceFile , checker ) {
var importedSymbol = checker . getSymbolAtLocation ( importSpecifier ) ;
if ( importedSymbol === undefined ) {
return false ;
}
var symbol = checker . getAliasedSymbol ( importedSymbol ) ;
if ( ! Lint . isSymbolFlagSet ( symbol , ts . SymbolFlags . Type ) ) {
return false ;
}
return ts . forEachChild ( sourceFile , function cb ( child ) {
if ( isImportLike ( child ) ) {
return false ;
}
var type = getImplicitType ( child , checker ) ;
// TODO: checker.typeEquals https://github.com/Microsoft/TypeScript/issues/13502
if ( type !== undefined && checker . typeToString ( type ) === checker . symbolToString ( symbol ) ) {
return true ;
}
return ts . forEachChild ( child , cb ) ;
2017-08-14 05:01:11 +02:00
} ) === true ;
2017-05-28 00:38:50 +02:00
}
function getImplicitType ( node , checker ) {
2017-08-14 05:01:11 +02:00
if ( ( utils . isPropertyDeclaration ( node ) || utils . isVariableDeclaration ( node ) ) &&
node . type === undefined && node . name . kind === ts . SyntaxKind . Identifier ||
utils . isBindingElement ( node ) && node . name . kind === ts . SyntaxKind . Identifier ) {
2017-05-28 00:38:50 +02:00
return checker . getTypeAtLocation ( node ) ;
}
else if ( utils . isSignatureDeclaration ( node ) && node . type === undefined ) {
var sig = checker . getSignatureFromDeclaration ( node ) ;
return sig === undefined ? undefined : sig . getReturnType ( ) ;
}
else {
return undefined ;
}
}
function isImportLike ( node ) {
return node . kind === ts . SyntaxKind . ImportDeclaration || node . kind === ts . SyntaxKind . ImportEqualsDeclaration ;
}
function forEachImport ( sourceFile , f ) {
return ts . forEachChild ( sourceFile , function ( child ) {
if ( isImportLike ( child ) ) {
var res = f ( child ) ;
if ( res !== undefined ) {
return res ;
}
}
return undefined ;
} ) ;
}
function findImport ( pos , sourceFile ) {
return forEachImport ( sourceFile , function ( i ) {
if ( i . kind === ts . SyntaxKind . ImportEqualsDeclaration ) {
if ( i . name . getStart ( ) === pos ) {
return i . name ;
}
}
else {
if ( i . importClause === undefined ) {
// Error node
return undefined ;
}
var _a = i . importClause , defaultName = _a . name , namedBindings = _a . namedBindings ;
if ( namedBindings !== undefined && namedBindings . kind === ts . SyntaxKind . NamespaceImport ) {
var name = namedBindings . name ;
if ( name . getStart ( ) === pos ) {
return name ;
}
return undefined ;
}
if ( defaultName !== undefined && defaultName . getStart ( ) === pos ) {
return defaultName ;
}
else if ( namedBindings !== undefined ) {
for ( var _i = 0 , _b = namedBindings . elements ; _i < _b . length ; _i ++ ) {
var name = _b [ _i ] . name ;
if ( name . getStart ( ) === pos ) {
return name ;
}
}
}
}
return undefined ;
} ) ;
}
function getUnusedDiagnostic ( diag ) {
switch ( diag . code ) {
case 6133 :
return 0 /* VARIABLE_OR_PARAMETER */ ; // "'{0}' is declared but never used.
case 6138 :
return 1 /* PROPERTY */ ; // "Property '{0}' is declared but never used."
default :
return undefined ;
}
}
var programToUnusedCheckedProgram = new WeakMap ( ) ;
function getUnusedCheckedProgram ( program , checkParameters ) {
// Assuming checkParameters will always have the same value, so only lookup by program.
var checkedProgram = programToUnusedCheckedProgram . get ( program ) ;
if ( checkedProgram !== undefined ) {
return checkedProgram ;
}
checkedProgram = makeUnusedCheckedProgram ( program , checkParameters ) ;
programToUnusedCheckedProgram . set ( program , checkedProgram ) ;
return checkedProgram ;
}
function makeUnusedCheckedProgram ( program , checkParameters ) {
2017-08-14 05:01:11 +02:00
var originalOptions = program . getCompilerOptions ( ) ;
var options = tslib _1 . _ _assign ( { } , originalOptions , { noEmit : true , noUnusedLocals : true , noUnusedParameters : originalOptions . noUnusedParameters || checkParameters } ) ;
var sourceFilesByName = new Map ( program . getSourceFiles ( ) . map ( function ( s ) { return [ getCanonicalFileName ( s . fileName ) , s ] ; } ) ) ;
2017-05-28 00:38:50 +02:00
// tslint:disable object-literal-sort-keys
return ts . createProgram ( Array . from ( sourceFilesByName . keys ( ) ) , options , {
2017-08-14 05:01:11 +02:00
fileExists : function ( f ) { return sourceFilesByName . has ( getCanonicalFileName ( f ) ) ; } ,
readFile : function ( f ) { return sourceFilesByName . get ( getCanonicalFileName ( f ) ) . text ; } ,
getSourceFile : function ( f ) { return sourceFilesByName . get ( getCanonicalFileName ( f ) ) ; } ,
2017-05-28 00:38:50 +02:00
getDefaultLibFileName : function ( ) { return ts . getDefaultLibFileName ( options ) ; } ,
writeFile : function ( ) { } ,
getCurrentDirectory : function ( ) { return "" ; } ,
getDirectories : function ( ) { return [ ] ; } ,
2017-08-14 05:01:11 +02:00
getCanonicalFileName : getCanonicalFileName ,
useCaseSensitiveFileNames : function ( ) { return ts . sys . useCaseSensitiveFileNames ; } ,
2017-05-28 00:38:50 +02:00
getNewLine : function ( ) { return "\n" ; } ,
} ) ;
// tslint:enable object-literal-sort-keys
2017-08-14 05:01:11 +02:00
// We need to be careful with file system case sensitivity
function getCanonicalFileName ( fileName ) {
return ts . sys . useCaseSensitiveFileNames ? fileName : fileName . toLowerCase ( ) ;
}
2017-05-28 00:38:50 +02:00
}
var _a , _b ;