From eb4314ecfc15c586a7dbcfd30909d4dd4230081d Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Wed, 8 Feb 2017 12:19:48 -0600 Subject: [PATCH] fix(lint): capture results of all linted files capture results of all linted files closes #725 --- src/lint.spec.ts | 1 + src/lint.ts | 76 +++++++----------------------- src/lint/lint-factory.ts | 24 ++++++++++ src/lint/lint-utils.spec.ts | 94 +++++++++++++++++++++++++++++++++++++ src/lint/lint-utils.ts | 70 +++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 60 deletions(-) create mode 100644 src/lint/lint-factory.ts create mode 100644 src/lint/lint-utils.spec.ts create mode 100644 src/lint/lint-utils.ts diff --git a/src/lint.spec.ts b/src/lint.spec.ts index 8a9ca1f2..77d013b6 100644 --- a/src/lint.spec.ts +++ b/src/lint.spec.ts @@ -2,6 +2,7 @@ import * as lint from './lint'; import * as workerClient from './worker-client'; import * as Constants from './util/constants'; + let originalEnv = process.env; describe('lint task', () => { diff --git a/src/lint.ts b/src/lint.ts index 6d2e7261..ba71a7ca 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -1,17 +1,15 @@ import { access } from 'fs'; import { BuildContext, ChangedFile, TaskInfo } from './util/interfaces'; -import { BuildError } from './util/errors'; -import { createProgram, findConfiguration, getFileNames } from 'tslint'; + +import { lintFile, LintResult, processLintResults } from './lint/lint-utils'; +import { createProgram, getFileNames } from './lint/lint-factory'; import { getUserConfigFile } from './util/config'; import * as Constants from './util/constants'; -import { readFileAsync, getBooleanPropertyValue } from './util/helpers'; +import { getBooleanPropertyValue } from './util/helpers'; import { join } from 'path'; import { Logger } from './logger/logger'; -import { printDiagnostics, DiagnosticsType } from './logger/logger-diagnostics'; -import { runTsLintDiagnostics } from './logger/logger-tslint'; + import { runWorker } from './worker-client'; -import * as Linter from 'tslint'; -import * as fs from 'fs'; import * as ts from 'typescript'; @@ -67,44 +65,18 @@ function lintApp(context: BuildContext, configFile: string) { const program = createProgram(configFile, context.srcDir); const files = getFileNames(program); - const promises = files.map(file => { - return lintFile(context, program, file); - }); - - return Promise.all(promises); + return lintFiles(context, program, files); } -function lintFiles(context: BuildContext, program: ts.Program, filePaths: string[]) { - const promises: Promise[] = []; - for (const filePath of filePaths) { - promises.push(lintFile(context, program, filePath)); - } - return Promise.all(promises); -} - -function lintFile(context: BuildContext, program: ts.Program, filePath: string): Promise { +export function lintFiles(context: BuildContext, program: ts.Program, filePaths: string[]) { return Promise.resolve().then(() => { - if (isMpegFile(filePath)) { - throw new Error(`${filePath} is not a valid TypeScript file`); + const promises: Promise[] = []; + for (const filePath of filePaths) { + promises.push(lintFile(context, program, filePath)); } - return readFileAsync(filePath); - }).then((fileContents: string) => { - const configuration = findConfiguration(null, filePath); - - const linter = new Linter(filePath, fileContents, { - configuration: configuration, - formatter: null, - formattersDirectory: null, - rulesDirectory: null, - }, program); - - const lintResult = linter.lint(); - if (lintResult && lintResult.failures && lintResult.failures.length) { - const diagnostics = runTsLintDiagnostics(context, lintResult.failures); - printDiagnostics(context, DiagnosticsType.TsLint, diagnostics, true, false); - throw new BuildError(`${filePath} did not pass TSLint`); - } - return lintResult; + return Promise.all(promises); + }).then((lintResults: LintResult[]) => { + return processLintResults(context, lintResults); }); } @@ -132,24 +104,6 @@ function getLintConfig(context: BuildContext, configFile: string): Promise { + describe('lintFile', () => { + it('should return lint details', () => { + // arrange + const mockLintResults: any = { + failures: [] + }; + const mockLinter = { + lint: () => { + return mockLintResults; + } + }; + const filePath = '/Users/dan/someFile.ts'; + const fileContent = 'someContent'; + const mockProgram: any = {}; + spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.resolve(fileContent)); + spyOn(lintFactory, lintFactory.getLinter.name).and.returnValue(mockLinter); + spyOn(fs, 'openSync').and.returnValue(null); + spyOn(fs, 'readSync').and.returnValue(null); + spyOn(fs, 'closeSync').and.returnValue(null); + // act + + const result = lintUtils.lintFile(null, mockProgram, filePath); + + // assert + return result.then((result: lintUtils.LintResult) => { + expect(result.filePath).toEqual(filePath); + expect(result.failures).toEqual(mockLintResults.failures); + expect(lintFactory.getLinter).toHaveBeenCalledWith(filePath, fileContent, mockProgram); + }); + }); + }); + + describe('processLintResults', () => { + it('should complete when no files have an error', () => { + // arrange + const lintResults: any[] = [ + { + failures: [], + filePath: '/Users/myFileOne.ts' + }, + { + failures: [], + filePath: '/Users/myFileTwo.ts' + } + ]; + + // act + lintUtils.processLintResults(null, lintResults); + + // assert + + }); + + it('should throw an error when one or more file has failures', () => { + // arrange + + spyOn(loggerDiagnostics, loggerDiagnostics.printDiagnostics.name).and.returnValue(null); + spyOn(tsLintLogger, tsLintLogger.runTsLintDiagnostics.name).and.returnValue(null); + const lintResults: any[] = [ + { + failures: [ + { } + ], + filePath: '/Users/myFileOne.ts' + }, + { + failures: [ + ], + filePath: '/Users/myFileTwo.ts' + } + ]; + const knownError = new Error('Should never get here'); + + // act + try { + lintUtils.processLintResults(null, lintResults); + throw knownError; + } catch (ex) { + expect(loggerDiagnostics.printDiagnostics).toHaveBeenCalledTimes(1); + expect(loggerDiagnostics.printDiagnostics).toHaveBeenCalledTimes(1); + expect(ex).not.toEqual(knownError); + } + }); + }); +}); diff --git a/src/lint/lint-utils.ts b/src/lint/lint-utils.ts new file mode 100644 index 00000000..607d4d64 --- /dev/null +++ b/src/lint/lint-utils.ts @@ -0,0 +1,70 @@ +import * as fs from 'fs'; +import { Program } from 'typescript'; +import { BuildError } from '../util/errors'; +import { getLinter } from './lint-factory'; +import { readFileAsync } from '../util/helpers'; +import { BuildContext } from '../util/interfaces'; +import { Logger } from '../logger/logger'; +import { printDiagnostics, DiagnosticsType } from '../logger/logger-diagnostics'; +import { runTsLintDiagnostics } from '../logger/logger-tslint'; + +export function isMpegFile(file: string) { + var buffer = new Buffer(256); + buffer.fill(0); + + const fd = fs.openSync(file, 'r'); + try { + fs.readSync(fd, buffer, 0, 256, null); + if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) { + Logger.debug(`tslint: ${file}: ignoring MPEG transport stream`); + return true; + } + } finally { + fs.closeSync(fd); + } + return false; +} + +export function lintFile(context: BuildContext, program: Program, filePath: string): Promise { + return Promise.resolve().then(() => { + if (isMpegFile(filePath)) { + throw new Error(`${filePath} is not a valid TypeScript file`); + } + return readFileAsync(filePath); + }).then((fileContents: string) => { + + const linter = getLinter(filePath, fileContents, program); + const lintResult = linter.lint(); + + return { + filePath: filePath, + failures: lintResult.failures + }; + }); +} + +export function processLintResults(context: BuildContext, lintResults: LintResult[]) { + const filesThatDidntPass: string[] = []; + for (const lintResult of lintResults) { + if (lintResult && lintResult.failures && lintResult.failures.length) { + const diagnostics = runTsLintDiagnostics(context, lintResult.failures); + printDiagnostics(context, DiagnosticsType.TsLint, diagnostics, true, false); + filesThatDidntPass.push(lintResult.filePath); + } + } + if (filesThatDidntPass.length) { + const errorMsg = generateFormattedErrorMsg(filesThatDidntPass); + throw new BuildError(errorMsg); + } +} + +export function generateFormattedErrorMsg(failingFiles: string[]) { + let listOfFilesString = ''; + failingFiles.forEach(file => listOfFilesString = listOfFilesString + file + '\n'); + return `The following files did not pass tslint: \n${listOfFilesString}`; +} + +export interface LintResult { + failures: any[]; + filePath: string; +};