From 8b67bd78d769cce7f13370c77b38c2e022eaf6df Mon Sep 17 00:00:00 2001 From: Ali Ijaz Sheikh Date: Mon, 4 Jun 2018 16:58:52 +0300 Subject: [PATCH] fix: use correct lint config for individual files When invoked with a list of files, linter configuration pertaining to those files should be preferred rather than the gts default config. --- src/lint.ts | 65 ++++++++++++++++++++++++++++++++--------------- test/test-lint.ts | 42 +++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/lint.ts b/src/lint.ts index 5c24fffb..b26ef826 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -28,28 +28,53 @@ import {Options} from './cli'; */ export function lint( options: Options, files: string[] = [], fix = false): boolean { - const configPath = - fs.existsSync(path.join(options.targetRootDir, 'tslint.json')) ? - path.resolve(options.targetRootDir, 'tslint.json') : - path.resolve(options.gtsRootDir, 'tslint.json'); - - const program = createProgram(options); - const configuration = Configuration.findConfiguration(configPath, '').results; - const linter = new Linter({fix, formatter: 'codeFrame'}, program); - const srcFiles = files.length > 0 ? files : Linter.getFileNames(program); - srcFiles.forEach(file => { - const sourceFile = program.getSourceFile(file); - if (sourceFile) { - const fileContents = sourceFile.getFullText(); - linter.lint(file, fileContents, configuration); + if (files.length > 0 ) { // manually provided filenames. + const rcs = files.map(file => { + // Different config files may apply to each file. + const configPath = Configuration.findConfigurationPath(null, file) || + path.join(options.gtsRootDir, 'tslint.json'); + + const configuration = Configuration.loadConfigurationFromPath(configPath, ''); + const source = fs.readFileSync(file, 'utf8'); + + const linter = new Linter({fix, formatter: 'codeFrame'}); + linter.lint(file, source, configuration); + const result = linter.getResult(); + if (result.errorCount || result.warningCount) { + options.logger.log(result.output); + return false; + } + return true; + }); + + return rcs.every(rc => rc); // if all files succeeded. + } else { + // Lint the set of files specified by the typescript program config. + const program = createProgram(options); + files = Linter.getFileNames(program); + + const configPath = + fs.existsSync(path.join(options.targetRootDir, 'tslint.json')) ? + path.resolve(options.targetRootDir, 'tslint.json') : + path.resolve(options.gtsRootDir, 'tslint.json'); + + const configuration = Configuration.loadConfigurationFromPath(configPath); + const linter = new Linter({fix, formatter: 'codeFrame'}, program); + + files.forEach(file => { + const sourceFile = program.getSourceFile(file); + if (sourceFile) { + const fileContents = sourceFile.getFullText(); + linter.lint(file, fileContents, configuration); + } + }); + const result = linter.getResult(); + if (result.errorCount || result.warningCount) { + options.logger.log(result.output); + return false; } - }); - const result = linter.getResult(); - if (result.errorCount || result.warningCount) { - options.logger.log(result.output); - return false; + return true; } - return true; } export function createProgram(options: Options): ts.Program { diff --git a/test/test-lint.ts b/test/test-lint.ts index 0bca9c50..27ce7fb2 100644 --- a/test/test-lint.ts +++ b/test/test-lint.ts @@ -135,20 +135,19 @@ test.serial('lint should lint only specified files', async t => { }); }); -test.serial('lint should not throw for unrecognized files', async t => { +test.serial('lint should throw for unrecognized files', async t => { await withFixtures( { 'tsconfig.json': JSON.stringify({}), 'a.ts': GOOD_CODE, }, async () => { - lint.lint(OPTIONS, ['z.ts']); - t.pass(); + t.throws(() => {lint.lint(OPTIONS, ['z.ts']);}); }); }); test.serial('lint should prefer user config file over default', async t => { - const CUSTOM_LINT_CODE = 'const t: Object;'; + const CUSTOM_LINT_CODE = 'debugger;'; // By defualt the above should fail lint. await withFixtures( @@ -157,8 +156,8 @@ test.serial('lint should prefer user config file over default', async t => { 'a.ts': CUSTOM_LINT_CODE }, async () => { - const okay = lint.lint(OPTIONS); - t.is(okay, false); + const okay = lint.lint(OPTIONS); + t.false(okay); }); // User should be able to override the default config. @@ -170,8 +169,37 @@ test.serial('lint should prefer user config file over default', async t => { }, async () => { const okay = lint.lint(OPTIONS); - t.is(okay, true); + t.true(okay); }); }); +test.serial('lint for specific files should use file-specific config', async t => { + const CODE_WITH_PARSEINT = 'parseInt(42);'; + let logBuffer = ''; + const optionsWithLog = Object.assign({}, OPTIONS, { + logger: { + log: (...args: string[]) => { + logBuffer += (args.join(' ')); + }, + error: nop, + dir: nop + } + }); + await withFixtures({ + dira: { + 'a.ts': CODE_WITH_PARSEINT, + // no tslint, so default should apply. + }, + dirb: { + 'b.ts': CODE_WITH_PARSEINT, + 'tslint.json': JSON.stringify({}) + } + }, async () => { + const okay = lint.lint(optionsWithLog, ['dira/a.ts', 'dirb/b.ts']); + t.false(okay); + t.regex(logBuffer, /dira\/a\.ts/); + t.notRegex(logBuffer, /dirb\/b\.ts/); + }); +}); + // TODO: test for when tsconfig.json is missing.