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 .
* /
var fs = require ( "fs" ) ;
var path = require ( "path" ) ;
var ts = require ( "typescript" ) ;
var configuration _1 = require ( "./configuration" ) ;
var enableDisableRules _1 = require ( "./enableDisableRules" ) ;
var error _1 = require ( "./error" ) ;
var formatterLoader _1 = require ( "./formatterLoader" ) ;
var rule _1 = require ( "./language/rule/rule" ) ;
var utils = require ( "./language/utils" ) ;
var ruleLoader _1 = require ( "./ruleLoader" ) ;
var utils _1 = require ( "./utils" ) ;
/ * *
* Linter that can lint multiple files in consecutive runs .
* /
var Linter = ( function ( ) {
function Linter ( options , program ) {
this . options = options ;
this . program = program ;
this . failures = [ ] ;
this . fixes = [ ] ;
if ( typeof options !== "object" ) {
throw new Error ( "Unknown Linter options type: " + typeof options ) ;
}
if ( options . configuration != null ) {
throw new Error ( "ILinterOptions does not contain the property `configuration` as of version 4. " +
"Did you mean to pass the `IConfigurationFile` object to lint() ? " ) ;
}
}
/ * *
* Creates a TypeScript program object from a tsconfig . json file path and optional project directory .
* /
Linter . createProgram = function ( configFile , projectDirectory ) {
if ( projectDirectory === void 0 ) { projectDirectory = path . dirname ( configFile ) ; }
var config = ts . readConfigFile ( configFile , ts . sys . readFile ) . config ;
var parseConfigHost = {
fileExists : fs . existsSync ,
readDirectory : ts . sys . readDirectory ,
readFile : function ( file ) { return fs . readFileSync ( file , "utf8" ) ; } ,
useCaseSensitiveFileNames : true ,
} ;
2017-08-14 05:01:11 +02:00
var parsed = ts . parseJsonConfigFileContent ( config , parseConfigHost , path . resolve ( projectDirectory ) , { noEmit : true } ) ;
2017-05-28 00:38:50 +02:00
var host = ts . createCompilerHost ( parsed . options , true ) ;
var program = ts . createProgram ( parsed . fileNames , parsed . options , host ) ;
return program ;
} ;
/ * *
* Returns a list of source file names from a TypeScript program . This includes all referenced
* files and excludes declaration ( ".d.ts" ) files .
* /
Linter . getFileNames = function ( program ) {
return program . getSourceFiles ( ) . map ( function ( s ) { return s . fileName ; } ) . filter ( function ( l ) { return l . substr ( - 5 ) !== ".d.ts" ; } ) ;
} ;
Linter . prototype . lint = function ( fileName , source , configuration ) {
if ( configuration === void 0 ) { configuration = configuration _1 . DEFAULT _CONFIG ; }
var sourceFile = this . getSourceFile ( fileName , source ) ;
var isJs = /\.jsx?$/i . test ( fileName ) ;
var enabledRules = this . getEnabledRules ( configuration , isJs ) ;
var fileFailures = this . getAllFailures ( sourceFile , enabledRules ) ;
if ( fileFailures . length === 0 ) {
// Usual case: no errors.
return ;
}
if ( this . options . fix && fileFailures . some ( function ( f ) { return f . hasFix ( ) ; } ) ) {
fileFailures = this . applyAllFixes ( enabledRules , fileFailures , sourceFile , fileName ) ;
}
// add rule severity to failures
2017-08-14 05:01:11 +02:00
var ruleSeverityMap = new Map ( enabledRules . map (
// tslint:disable-next-line no-unnecessary-type-assertion
function ( rule ) { return [ rule . getOptions ( ) . ruleName , rule . getOptions ( ) . ruleSeverity ] ; } ) ) ;
2017-05-28 00:38:50 +02:00
for ( var _i = 0 , fileFailures _1 = fileFailures ; _i < fileFailures _1 . length ; _i ++ ) {
var failure = fileFailures _1 [ _i ] ;
var severity = ruleSeverityMap . get ( failure . getRuleName ( ) ) ;
if ( severity === undefined ) {
throw new Error ( "Severity for rule '" + failure . getRuleName ( ) + "' not found" ) ;
}
failure . setRuleSeverity ( severity ) ;
}
this . failures = this . failures . concat ( fileFailures ) ;
} ;
Linter . prototype . getResult = function ( ) {
var formatter ;
var formattersDirectory = configuration _1 . getRelativePath ( this . options . formattersDirectory ) ;
var formatterName = this . options . formatter !== undefined ? this . options . formatter : "prose" ;
var Formatter = formatterLoader _1 . findFormatter ( formatterName , formattersDirectory ) ;
if ( Formatter !== undefined ) {
formatter = new Formatter ( ) ;
}
else {
throw new Error ( "formatter '" + formatterName + "' not found" ) ;
}
var output = formatter . format ( this . failures , this . fixes ) ;
var errorCount = this . failures . filter ( function ( failure ) { return failure . getRuleSeverity ( ) === "error" ; } ) . length ;
return {
errorCount : errorCount ,
failures : this . failures ,
fixes : this . fixes ,
format : formatterName ,
output : output ,
warningCount : this . failures . length - errorCount ,
} ;
} ;
Linter . prototype . getAllFailures = function ( sourceFile , enabledRules ) {
var _this = this ;
var failures = utils _1 . flatMap ( enabledRules , function ( rule ) { return _this . applyRule ( rule , sourceFile ) ; } ) ;
return enableDisableRules _1 . removeDisabledFailures ( sourceFile , failures ) ;
} ;
Linter . prototype . applyAllFixes = function ( enabledRules , fileFailures , sourceFile , sourceFileName ) {
// When fixing, we need to be careful as a fix in one rule may affect other rules.
// So fix each rule separately.
var source = sourceFile . text ;
var _loop _1 = function ( rule ) {
var hasFixes = fileFailures . some ( function ( f ) { return f . hasFix ( ) && f . getRuleName ( ) === rule . getOptions ( ) . ruleName ; } ) ;
if ( hasFixes ) {
// Get new failures in case the file changed.
var updatedFailures = enableDisableRules _1 . removeDisabledFailures ( sourceFile , this _1 . applyRule ( rule , sourceFile ) ) ;
var fixableFailures = updatedFailures . filter ( function ( f ) { return f . hasFix ( ) ; } ) ;
this _1 . fixes = this _1 . fixes . concat ( fixableFailures ) ;
source = this _1 . applyFixes ( sourceFileName , source , fixableFailures ) ;
sourceFile = this _1 . getSourceFile ( sourceFileName , source ) ;
}
} ;
var this _1 = this ;
for ( var _i = 0 , enabledRules _1 = enabledRules ; _i < enabledRules _1 . length ; _i ++ ) {
var rule = enabledRules _1 [ _i ] ;
_loop _1 ( rule ) ;
}
// If there were fixes, get the *new* list of failures.
return this . getAllFailures ( sourceFile , enabledRules ) ;
} ;
// Only "protected" because a test directly accesses it.
// tslint:disable-next-line member-ordering
Linter . prototype . applyFixes = function ( sourceFilePath , source , fixableFailures ) {
2017-08-14 05:01:11 +02:00
var _this = this ;
2017-05-28 00:38:50 +02:00
var fixesByFile = createMultiMap ( fixableFailures , function ( f ) { return [ f . getFileName ( ) , f . getFix ( ) ] ; } ) ;
fixesByFile . forEach ( function ( fileFixes , filePath ) {
var fileNewSource ;
if ( path . resolve ( filePath ) === path . resolve ( sourceFilePath ) ) {
source = rule _1 . Replacement . applyFixes ( source , fileFixes ) ;
fileNewSource = source ;
}
else {
var oldSource = fs . readFileSync ( filePath , "utf-8" ) ;
fileNewSource = rule _1 . Replacement . applyFixes ( oldSource , fileFixes ) ;
}
2017-08-14 05:01:11 +02:00
fs . writeFileSync ( filePath , fileNewSource ) ;
_this . updateProgram ( filePath ) ;
2017-05-28 00:38:50 +02:00
} ) ;
return source ;
} ;
2017-08-14 05:01:11 +02:00
Linter . prototype . updateProgram = function ( sourceFilePath ) {
if ( this . program !== undefined && this . program . getSourceFile ( sourceFilePath ) !== undefined ) {
var options = this . program . getCompilerOptions ( ) ;
this . program = ts . createProgram ( this . program . getRootFileNames ( ) , options , ts . createCompilerHost ( options , true ) , this . program ) ;
}
} ;
2017-05-28 00:38:50 +02:00
Linter . prototype . applyRule = function ( rule , sourceFile ) {
try {
if ( this . program !== undefined && rule _1 . isTypedRule ( rule ) ) {
return rule . applyWithProgram ( sourceFile , this . program ) ;
}
else {
return rule . apply ( sourceFile ) ;
}
}
catch ( error ) {
2017-08-14 05:01:11 +02:00
if ( error _1 . isError ( error ) && error . stack !== undefined ) {
error _1 . showWarningOnce ( error . stack ) ;
2017-05-28 00:38:50 +02:00
}
else {
2017-08-14 05:01:11 +02:00
error _1 . showWarningOnce ( String ( error ) ) ;
2017-05-28 00:38:50 +02:00
}
return [ ] ;
}
} ;
Linter . prototype . getEnabledRules = function ( configuration , isJs ) {
if ( configuration === void 0 ) { configuration = configuration _1 . DEFAULT _CONFIG ; }
var ruleOptionsList = configuration _1 . convertRuleOptions ( isJs ? configuration . jsRules : configuration . rules ) ;
var rulesDirectories = utils _1 . arrayify ( this . options . rulesDirectory )
. concat ( utils _1 . arrayify ( configuration . rulesDirectory ) ) ;
return ruleLoader _1 . loadRules ( ruleOptionsList , rulesDirectories , isJs ) ;
} ;
Linter . prototype . getSourceFile = function ( fileName , source ) {
if ( this . program !== undefined ) {
var sourceFile = this . program . getSourceFile ( fileName ) ;
if ( sourceFile === undefined ) {
2017-08-14 05:01:11 +02:00
var INVALID _SOURCE _ERROR = ( _a = [ "\n Invalid source file: " , ". Ensure that the files supplied to lint have a .ts, .tsx, .d.ts, .js or .jsx extension.\n " ] , _a . raw = [ "\n Invalid source file: " , ". Ensure that the files supplied to lint have a .ts, .tsx, .d.ts, .js or .jsx extension.\n " ] , utils _1 . dedent ( _a , fileName ) ) ;
throw new error _1 . FatalError ( INVALID _SOURCE _ERROR ) ;
2017-05-28 00:38:50 +02:00
}
return sourceFile ;
}
else {
return utils . getSourceFile ( fileName , source ) ;
}
var _a ;
} ;
2017-08-14 05:01:11 +02:00
Linter . VERSION = "5.6.0" ;
Linter . findConfiguration = configuration _1 . findConfiguration ;
Linter . findConfigurationPath = configuration _1 . findConfigurationPath ;
Linter . getRulesDirectories = configuration _1 . getRulesDirectories ;
Linter . loadConfigurationFromPath = configuration _1 . loadConfigurationFromPath ;
2017-05-28 00:38:50 +02:00
return Linter ;
} ( ) ) ;
function createMultiMap ( inputs , getPair ) {
var map = new Map ( ) ;
for ( var _i = 0 , inputs _1 = inputs ; _i < inputs _1 . length ; _i ++ ) {
var input = inputs _1 [ _i ] ;
var pair = getPair ( input ) ;
if ( pair !== undefined ) {
var k = pair [ 0 ] , v = pair [ 1 ] ;
var vs = map . get ( k ) ;
if ( vs !== undefined ) {
vs . push ( v ) ;
}
else {
map . set ( k , [ v ] ) ;
}
}
}
return map ;
}
module . exports = Linter ;