diff --git a/README.md b/README.md index 6ccca6c..43d0846 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TSLint for MSBuild -An MSBuild wrapper around Palantir's wonderful [tslint](https://github.com/palantir/tslint). Get it at [nuget.org](https://www.nuget.org/packages/TSLint.MSBuild/). +An MSBuild target for linting TypeScript code using [TSLint](https://github.com/palantir/tslint). Get it at [nuget.org](https://www.nuget.org/packages/TSLint.MSBuild/). ## Usage @@ -14,11 +14,13 @@ A .js runner file then takes in the path to that file list, scans for `tslint.js The following properties may be overidden via your targets: * **TSLintDeleteFileListFile** - Whether to delete the file list file when done. Defaults to `true`. +* **TSLintExclude** - A JavaScript RegExp literal of matching file names to exclude. Defaults to `"^$"` (none). +* **TSLintFilesRootDir** - A root directory to work within. Defaults to `$(MSBuildProjectDirectory)`. * **TSLintFileListDir** - The directory to put the file list in. Defaults to `$(IntermediateOutDir)`. * **TSLintFileListName** - The name of the file list file. Defaults to `TSLintFileList.txt-$(MSBuildProjectName)`. * **TSLintNodeExe**: A node executable to execute the runner script. Defaults to the `tools\node-5.9.0.exe` in the package. * **TSLintRunnerScript** - The .js file to take in `TSLintFileListFile`. Defaults to the `tools\runner.js` in the package. -* **TSLintFilesRootDir** - A root directory to work within. Defaults to `$(MSBuildProjectDirectory)`. + ### tslint.json diff --git a/TSLint.MSBuild.nuspec b/TSLint.MSBuild.nuspec index 22c0ca2..1e5b2b4 100644 --- a/TSLint.MSBuild.nuspec +++ b/TSLint.MSBuild.nuspec @@ -2,18 +2,18 @@ TSLint.MSBuild - 0.1.3 + 0.2.0 palantir, joshuakgoldberg joshuakgoldberg https://github.com/joshuakgoldberg/TSLint.MSBuild/blob/master/LICENSE.md https://github.com/joshuakgoldberg/TSLint.MSBuild false - An MSBuild target for Palantir's wonderful TSLint. + An MSBuild target for linting TypeScript code using TSLint. tslint, msbuild - + diff --git a/package.json b/package.json index 4b25e05..7b540ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tslint-msbuild", - "version": "0.1.3", - "description": " An MSBuild target for Palantir's wonderful TSLint.", + "version": "0.2.0", + "description": " An MSBuild target for linting TypeScript code using TSLint.", "author": "Joshua K Goldberg", "license": "MIT", "repository": { diff --git a/src/ArgumentsCollection.ts b/src/ArgumentsCollection.ts new file mode 100644 index 0000000..22a51f5 --- /dev/null +++ b/src/ArgumentsCollection.ts @@ -0,0 +1,105 @@ +namespace TSLint.MSBuild { + "use strict"; + + /** + * A parser and storer for command-line arguments to TSLint.MSBuild. + */ + export class ArgumentsCollection { + /** + * Value setters for arguments, keyed by alias. + */ + private static valueSetters: { [i: string]: (value: string) => void } = { + "exclude": ArgumentsCollection.prototype.setExclude, + "files-root-dir": ArgumentsCollection.prototype.setFilesRootDir, + "file-list-file": ArgumentsCollection.prototype.setFileListFile + }; + + /** + * A glob path to exclude from linting. + * + * @alias exclude + */ + private exclude: RegExp; + + /** + * A root directory to work within. + * + * @alias files-root-dir + */ + private filesRootDir: string; + + /** + * The path to the file listing files to be linted. + * + * @alias file-list-file + */ + private fileListFile: string; + + /** + * Initializes a new instance of the ArgumentsCollection class. + * + * @param inputs Raw command-line input. + */ + constructor(inputs: string[]) { + for (let i: number = 0; i < inputs.length; i += 2) { + const alias = inputs[i].replace("-", ""); + const value = inputs[i + 1]; + + if (!ArgumentsCollection.valueSetters.hasOwnProperty(alias)) { + throw new Error(`Unknown TSLint.MSBuild argument: '${inputs[i]}' '${value}'`); + } + + console.log(`Setting '${alias}' to '${value}'.`); + ArgumentsCollection.valueSetters[alias].call(this, value); + } + } + + /** + * @returns The FilesRootDir argument. + */ + public getFilesRootDir(): string { + return this.filesRootDir; + } + + /** + * @returns The FileListFile argument. + */ + public getFileListFile(): string { + return this.fileListFile; + } + + /** + * @returns The Exclude argument. + */ + public getExclude(): RegExp { + return this.exclude; + } + + /** + * Sets the FilesRootDir argument. + * + * @param value A new FilesRootDir value. + */ + private setFilesRootDir(value: string): void { + this.filesRootDir = value; + } + + /** + * Sets the FileListFile argument. + * + * @param value A new FileListFile value. + */ + private setFileListFile(value: string): void { + this.fileListFile = value; + } + + /** + * Sets the Exclude argument. + * + * @param value A new Exclude value. + */ + private setExclude(value: string): void { + this.exclude = new RegExp(value || "^$", "i"); + } + } +} diff --git a/src/ConfigLoader.ts b/src/ConfigLoader.ts deleted file mode 100644 index 7ec1b5a..0000000 --- a/src/ConfigLoader.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// - -namespace TSLint.MSBuild { - "use strict"; - - const fs = require("fs"), - path = require("path"); - - export class ConfigLoader { - - /** - * Reads and deserializes a tslint.json file. - * @param path The path of the file. - * @returns A promise with the configuration object. - */ - public readJSONConfig(path: string) { - return new Promise((resolve, reject) => { - fs.readFile(path, (error, result) => { - if (error) { - reject(error); - } else { - resolve(JSON.parse(ConfigLoader.stripBomIfNeeded(result.toString()))); - } - }); - }); - } - - /** - * Strips BOM if any. - * @param content the raw content of a file. - * @returns The content with the BOM stripped. - */ - static stripBomIfNeeded(content: string) { - return content.replace(/^\uFEFF/, ""); - } - } -} \ No newline at end of file diff --git a/src/Folder.ts b/src/Folder.ts index 5075668..958264b 100644 --- a/src/Folder.ts +++ b/src/Folder.ts @@ -1,12 +1,13 @@ /// +/// /// -/// namespace TSLint.MSBuild { "use strict"; let fs = require("fs"), path = require("path"); + /** * A representation of a directory with files and optionally a tsconfig.json. */ @@ -35,9 +36,8 @@ namespace TSLint.MSBuild { * Initializes a new instance of the Folder class. * * @param path The path to this folder. - * @param configLoader The configuration loader. */ - constructor(path: string, private configLoader: ConfigLoader) { + constructor(path: string) { this.path = path; } @@ -88,18 +88,22 @@ namespace TSLint.MSBuild { */ public loadTSLintConfig(): Promise { this.loadWaiter.markActionStart(); - return this.configLoader - .readJSONConfig(path.join(this.path, "tslint.json")) - .then((config) => { + + return new Promise(resolve => { + fs.readFile(path.join(this.path, "tslint.json"), (error, result) => { + if (error) { + this.setTSLintConfig(undefined); + resolve(false); + return; + } + this.setTSLintConfig({ formatter: "json", - configuration: config + configuration: this.sanitizeFileContents(result) }); - return true; - }) - .catch((error) => { - this.setTSLintConfig(undefined); + resolve(true); }); + }); } /** @@ -112,5 +116,15 @@ namespace TSLint.MSBuild { return this.loadWaiter.addCallback(() => resolve(this)); }); } + + /** + * Sanitizes a file's contents in case of an odd BOM. + * + * @param raw Raw contents of a file. + * @returns The BOM-sanitized equivalent text. + */ + private sanitizeFileContents(raw: any): string { + return JSON.parse(raw.toString().replace(/^\uFEFF/, "")); + } } } diff --git a/src/FolderCollection.ts b/src/FolderCollection.ts index ef64ec8..57b55a3 100644 --- a/src/FolderCollection.ts +++ b/src/FolderCollection.ts @@ -1,4 +1,5 @@ /// +/// /// namespace TSLint.MSBuild { @@ -9,9 +10,9 @@ namespace TSLint.MSBuild { */ export class FolderCollection { /** - * The root directory to look at files under. + * Parsed arguments to the program. */ - private rootDirectory: string; + private argumentsCollection: ArgumentsCollection; /** * Known folders that have been added. @@ -21,11 +22,10 @@ namespace TSLint.MSBuild { /** * Initializes a new instance of the LintRunner class. * - * @param rootDirectory The root directory to look at files under. - * @param ConfigLoader The configuration loader. + * @param argumentsCollection Parsed arguments to the program. */ - constructor(rootDirectory: string, private configLoader: ConfigLoader) { - this.rootDirectory = rootDirectory; + constructor(argumentsCollection: ArgumentsCollection) { + this.argumentsCollection = argumentsCollection; } /** @@ -55,6 +55,7 @@ namespace TSLint.MSBuild { * Adds a file path and its containing folder path. * * @param filePath A path to a file. + * @returns A promise of the file being added. */ private addFilePath(filePath: string): Promise { return this @@ -75,7 +76,7 @@ namespace TSLint.MSBuild { return new Promise(resolve => resolve(folder)); } - folder = this.folders[folderPath] = new Folder(folderPath, this.configLoader); + folder = this.folders[folderPath] = new Folder(folderPath); return folder .loadTSLintConfig() @@ -113,7 +114,7 @@ namespace TSLint.MSBuild { * @todo Should this reject instead of resolve with undefined? */ private checkFolderParent(folderPath: string): Promise { - if (folderPath.length < this.rootDirectory.length) { + if (folderPath.length < this.argumentsCollection.getFilesRootDir().length) { return new Promise(resolve => resolve(undefined)); } diff --git a/src/LintRunner.ts b/src/LintRunner.ts index 571bf68..dd66ace 100644 --- a/src/LintRunner.ts +++ b/src/LintRunner.ts @@ -1,4 +1,5 @@ /// +/// /// /// /// @@ -12,11 +13,6 @@ namespace TSLint.MSBuild { * Driver for running TSLint on a number of files. */ export class LintRunner { - /** - * The root directory to look at files under. - */ - private rootDirectory: string; - /** * Folders generated from individual file paths. */ @@ -35,12 +31,10 @@ namespace TSLint.MSBuild { /** * Initializes a new instance of the LintRunner class. * - * @param rootDirectory The root directory to look at files under. - * @param configLoader The configuration loader. + * @param argumentsCollection Parsed arguments to the program. */ - constructor(rootDirectory: string, private configLoader: ConfigLoader) { - this.rootDirectory = rootDirectory; - this.folders = new FolderCollection(rootDirectory, this.configLoader); + constructor(argumentsCollection: ArgumentsCollection) { + this.folders = new FolderCollection(argumentsCollection); this.tsLintSearcher = new TSLintSearcher(); this.tsLint = require(this.tsLintSearcher.resolve()); } diff --git a/src/TSLint.MSBuild.targets b/src/TSLint.MSBuild.targets index da5c42b..3951810 100644 --- a/src/TSLint.MSBuild.targets +++ b/src/TSLint.MSBuild.targets @@ -11,12 +11,13 @@ Name="TSLint"> true + ^$ + $(MSBuildProjectDirectory) $(IntermediateOutDir) TSLintFileList-$(MSBuildProjectName).txt $(TSLintFileListDir)$(TSLintFileListName) $([System.IO.Path]::GetFullPath("$(MSBuildThisFileDirectory)\..\tools\node-5.9.0.exe")) $([System.IO.Path]::GetFullPath("$(MSBuildThisFileDirectory)\..\tools\runner.js")) - $(MSBuildProjectDirectory) @@ -32,7 +33,7 @@ diff --git a/src/index.ts b/src/index.ts index cc6b83d..f69263f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ /// +/// /// /// /// @@ -43,20 +44,22 @@ namespace TSLint.MSBuild { } (() => { - let rootDirectory: string = process.argv[2], - summaryFilePath: string = process.argv[3], + let argumentsCollection: ArgumentsCollection = new ArgumentsCollection(process.argv.slice(2)), + rootDirectory: string = argumentsCollection.getFilesRootDir(), + summaryFilePath: string = argumentsCollection.getFileListFile(), filePaths: string[] = getInputFilesList(summaryFilePath), - runner = new LintRunner(rootDirectory, new ConfigLoader()); + filePathsIncluded: string[] = filePaths.filter( + filePath => !argumentsCollection.getExclude().test(filePath)), + runner = new LintRunner(argumentsCollection); - console.log(`Running TSLint on ${filePaths.length} files.`); - console.log(`Root directory is '${rootDirectory}'.`); + console.log(`Running TSLint on ${filePathsIncluded.length} of ${filePaths.length} file(s) (excluding ${filePaths.length - filePathsIncluded.length}).`); runner - .addFilePaths(filePaths) + .addFilePaths(filePathsIncluded) .then(() => runner.runTSLint()) .then(lintErrors => { if (lintErrors.length === 0) { - console.log(`0 errors found in ${filePaths.length} files.`); + console.log(`0 errors found in ${filePathsIncluded.length} file(s).`); return; } @@ -65,7 +68,7 @@ namespace TSLint.MSBuild { .join("\n"); console.error(lintErrorsFormatted); - console.log(`${lintErrors.length} errors found in ${filePaths.length} files.`); + console.log(`${lintErrors.length} error(s) found in ${filePathsIncluded.length} file(s).`); }) .catch(error => { console.error("Error running TSLint!");