diff --git a/README.md b/README.md index c692927185a..834a7e7c761 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Options: -h, --help display detailed help -i, --init generate a tslint.json config file in the current working directory -o, --out output file ---project tsconfig.json file +-p, --project tsconfig.json file -r, --rules-dir rules directory -s, --formatters-dir formatters directory -t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist) [default: "prose"] @@ -200,7 +200,7 @@ tslint accepts the following command-line options: the tests. See the full tslint documentation for more details on how this can be used to test custom rules. ---project: +-p, --project: The location of a tsconfig.json file that will be used to determine which files will be linted. diff --git a/docs/usage/cli/index.md b/docs/usage/cli/index.md index 85b5fca9c57..c426be03d08 100644 --- a/docs/usage/cli/index.md +++ b/docs/usage/cli/index.md @@ -36,7 +36,7 @@ Options: -h, --help display detailed help -i, --init generate a tslint.json config file in the current working directory -o, --out output file ---project tsconfig.json file +-p, --project tsconfig.json file -r, --rules-dir rules directory -s, --formatters-dir formatters directory -t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist, codeFrame) [default: "prose"] @@ -115,7 +115,7 @@ tslint accepts the following command-line options: specified directory as the configuration file for the tests. See the full tslint documentation for more details on how this can be used to test custom rules. ---project: +-p, --project: The location of a tsconfig.json file that will be used to determine which files will be linted. diff --git a/src/runner.ts b/src/runner.ts index 77b5b8c7560..825376a5880 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -112,35 +112,30 @@ export class Runner { public run(onComplete: (status: number) => void) { if (this.options.version) { this.outputStream.write(Linter.VERSION + "\n"); - onComplete(0); - return; + return onComplete(0); } if (this.options.init) { if (fs.existsSync(CONFIG_FILENAME)) { console.error(`Cannot generate ${CONFIG_FILENAME}: file already exists`); - onComplete(1); - return; + return onComplete(1); } const tslintJSON = JSON.stringify(DEFAULT_CONFIG, undefined, " "); fs.writeFileSync(CONFIG_FILENAME, tslintJSON); - onComplete(0); - return; + return onComplete(0); } if (this.options.test) { - const results = runTests(this.options.test, this.options.rulesDirectory); + const results = runTests((this.options.files || []).map(Runner.trimSingleQuotes), this.options.rulesDirectory); const didAllTestsPass = consoleTestResultsHandler(results); - onComplete(didAllTestsPass ? 0 : 1); - return; + return onComplete(didAllTestsPass ? 0 : 1); } // when provided, it should point to an existing location if (this.options.config && !fs.existsSync(this.options.config)) { console.error("Invalid option for configuration: " + this.options.config); - onComplete(1); - return; + return onComplete(1); } // if both files and tsconfig are present, use files @@ -150,8 +145,7 @@ export class Runner { if (this.options.project != null) { if (!fs.existsSync(this.options.project)) { console.error("Invalid option for project: " + this.options.project); - onComplete(1); - return; + return onComplete(1); } program = Linter.createProgram(this.options.project, path.dirname(this.options.project)); if (files.length === 0) { @@ -171,12 +165,16 @@ export class Runner { message += " " + ts.flattenDiagnosticMessageText(diag.messageText, "\n"); return message; }); - throw new Error(messages.join("\n")); + console.error(messages.join("\n")); + return onComplete(this.options.force ? 0 : 1); } } else { // if not type checking, we don't need to pass in a program object program = undefined; } + } else if (this.options.typeCheck) { + console.error("--project must be specified in order to enable type checking."); + return onComplete(1); } let ignorePatterns: string[] = []; @@ -197,7 +195,7 @@ export class Runner { } catch (error) { if (error.name === FatalError.NAME) { console.error(error.message); - onComplete(1); + return onComplete(1); } // rethrow unhandled error throw error; @@ -218,21 +216,19 @@ export class Runner { for (const file of files) { if (!fs.existsSync(file)) { console.error(`Unable to open file: ${file}`); - onComplete(1); - return; + return onComplete(1); } const buffer = new Buffer(256); - buffer.fill(0); const fd = fs.openSync(file, "r"); try { fs.readSync(fd, buffer, 0, 256, 0); - if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) { + if (buffer.readInt8(0, true) === 0x47 && buffer.readInt8(188, true) === 0x47) { // MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame // separator, repeating every 188 bytes. It is unlikely to find that pattern in // TypeScript source, so tslint ignores files with the specific pattern. console.warn(`${file}: ignoring MPEG transport stream`); - return; + continue; } } finally { fs.closeSync(fd); diff --git a/src/test.ts b/src/test.ts index ed3510205eb..151b2083bb1 100644 --- a/src/test.ts +++ b/src/test.ts @@ -44,9 +44,12 @@ export interface TestResult { }; } -export function runTests(pattern: string, rulesDirectory?: string | string[]): TestResult[] { - return glob.sync(`${pattern}/tslint.json`) - .map((directory: string): TestResult => runTest(path.dirname(directory), rulesDirectory)); +export function runTests(patterns: string[], rulesDirectory?: string | string[]): TestResult[] { + const files: string[] = []; + for (const pattern of patterns) { + files.push(...glob.sync(`${pattern}/tslint.json`)); + } + return files.map((directory: string): TestResult => runTest(path.dirname(directory), rulesDirectory)); } export function runTest(testDirectory: string, rulesDirectory?: string | string[]): TestResult { diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index 8fbfbb6b87f..8f3566e4e1e 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -70,7 +70,8 @@ const processed = optimist describe: "output file", type: "string", }, - "project": { + "p": { + alias: "project", describe: "tsconfig.json file", type: "string", }, @@ -92,7 +93,7 @@ const processed = optimist }, "test": { describe: "test that tslint produces the correct output for the specified directory", - type: "string", + type: "boolean", }, "type-check": { describe: "enable type checking when linting a project", @@ -186,7 +187,7 @@ tslint accepts the following commandline options: the tests. See the full tslint documentation for more details on how this can be used to test custom rules. - --project: + -p, --project: The location of a tsconfig.json file that will be used to determine which files will be linted. @@ -213,7 +214,7 @@ const options: IRunnerOptions = { formattersDirectory: argv.s, init: argv.init, out: argv.out, - project: argv.project, + project: argv.p, rulesDirectory: argv.r, test: argv.test, typeCheck: argv["type-check"], diff --git a/test/executable/executableTests.ts b/test/executable/executableTests.ts index 346ea297db6..73517a40c52 100644 --- a/test/executable/executableTests.ts +++ b/test/executable/executableTests.ts @@ -210,6 +210,15 @@ describe("Executable", function(this: Mocha.ISuiteCallbackContext) { done(); }); }); + + it("can be used with multiple paths", (done) => { + // pass a failing test as second path to make sure it gets executed + execCli(["--test", "test/files/custom-rule-rule-test", "test/files/incorrect-fixes-test"], (err) => { + assert.isNotNull(err, "process should exit with error"); + assert.strictEqual(err.code, 1, "error code should be 1"); + done(); + }); + }); }); describe("tsconfig.json", () => { @@ -230,7 +239,7 @@ describe("Executable", function(this: Mocha.ISuiteCallbackContext) { }); it("exits with code 0 if `tsconfig.json` is passed but it includes no ts files", (done) => { - execCli(["-c", "test/files/tsconfig-no-ts-files/tslint.json", "--project", "test/files/tsconfig-no-ts-files/tsconfig.json"], + execCli(["-c", "test/files/tsconfig-no-ts-files/tslint.json", "-p", "test/files/tsconfig-no-ts-files/tsconfig.json"], (err) => { assert.isNull(err, "process should exit without an error"); done(); @@ -238,6 +247,16 @@ describe("Executable", function(this: Mocha.ISuiteCallbackContext) { }); }); + describe("--type-check", () => { + it("exits with code 1 if --project is not passed", (done) => { + execCli(["--type-check"], (err) => { + assert.isNotNull(err, "process should exit with error"); + assert.strictEqual(err.code, 2, "error code should be 2"); + done(); + }); + }); + }); + describe("--init flag", () => { // remove temp file before calling tslint --init beforeEach(cleanTempInitFile);