From fd54825a8d173d7211b8722541d407ce0a4fd1d5 Mon Sep 17 00:00:00 2001 From: Brian Olore Date: Tue, 28 Feb 2017 23:39:03 -0500 Subject: [PATCH 1/8] Support for ERROR & WARN per rule (#1738) --- CHANGELOG.md | 36 +++++++++ docs/_data/formatters.json | 6 +- docs/formatters/pmd/index.html | 2 +- docs/formatters/prose/index.html | 2 +- docs/formatters/verbose/index.html | 2 +- src/configuration.ts | 33 +++++++- src/enableDisableRules.ts | 4 +- src/formatters/checkstyleFormatter.ts | 5 +- src/formatters/msbuildFormatter.ts | 5 +- src/formatters/pmdFormatter.ts | 7 +- src/formatters/proseFormatter.ts | 6 +- src/formatters/stylishFormatter.ts | 35 +++++---- src/formatters/verboseFormatter.ts | 15 ++-- src/formatters/vsoFormatter.ts | 6 +- src/index.ts | 3 +- src/language/formatter/formatter.ts | 4 +- src/language/rule/abstractRule.ts | 26 +++---- src/language/rule/rule.ts | 16 ++++ src/language/walker/walkContext.ts | 2 +- src/linter.ts | 18 ++++- src/ruleLoader.ts | 4 +- src/rules/eoflineRule.ts | 3 +- src/rules/maxFileLineCountRule.ts | 5 +- src/rules/noImportSideEffectRule.ts | 2 +- src/runner.ts | 6 +- test/config/tslint-custom-rules-with-dir.json | 8 +- .../tslint-custom-rules-with-two-dirs.json | 24 ++++-- test/config/tslint-custom-rules.json | 8 +- test/config/tslint-extends-builtin.json | 8 +- test/config/tslint-extends-package-array.json | 8 +- .../tslint-extends-package-boolean.json | 11 +++ .../tslint-extends-package-two-levels.json | 8 +- test/config/tslint-extends-package.json | 16 +++- test/config/tslint-extends-relative.json | 8 +- test/config/tslint-with-comments.json | 9 ++- test/config/tslint-with-jsrules.json | 4 +- test/configurationTests.ts | 78 +++++++++++++++---- test/files/custom-rules/alwaysFailRule.js | 2 +- test/formatters/checkstyleFormatterTests.ts | 23 +++--- test/formatters/codeFrameFormatterTests.ts | 11 +-- test/formatters/externalFormatterTests.ts | 9 ++- test/formatters/fileslistFormatterTests.ts | 7 +- test/formatters/jsonFormatterTests.ts | 15 ++-- test/formatters/msbuildFormatterTests.ts | 19 ++--- test/formatters/pmdFormatterTests.ts | 19 ++--- test/formatters/proseFormatterTests.ts | 25 +++--- test/formatters/stylishFormatterTests.ts | 31 +++----- test/formatters/utils.ts | 31 ++++++++ test/formatters/verboseFormatterTests.ts | 15 ++-- test/formatters/vsoFormatterTests.ts | 9 ++- test/ruleLoaderTests.ts | 43 ++++++++++ 51 files changed, 500 insertions(+), 202 deletions(-) create mode 100644 test/config/tslint-extends-package-boolean.json create mode 100644 test/formatters/utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e1947c46b..9f1797a9cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,42 @@ Change Log === + + v4.5.1 --- diff --git a/docs/_data/formatters.json b/docs/_data/formatters.json index 1617406a14b..dec1dacbcce 100644 --- a/docs/_data/formatters.json +++ b/docs/_data/formatters.json @@ -36,13 +36,13 @@ "formatterName": "pmd", "description": "Formats errors as through they were PMD output.", "descriptionDetails": "Imitates the XML output from PMD. All errors have a priority of 1.", - "sample": "\n\n \n \n \n", + "sample": "\n\n \n \n \n", "consumer": "machine" }, { "formatterName": "prose", "description": "The default formatter which outputs simple human-readable messages.", - "sample": "myFile.ts[1, 14]: Missing semicolon", + "sample": "ERROR: myFile.ts[1, 14]: Missing semicolon", "consumer": "human" }, { @@ -56,7 +56,7 @@ "formatterName": "verbose", "description": "The human-readable formatter which includes the rule name in messages.", "descriptionDetails": "The output is the same as the prose formatter with the rule name included", - "sample": "(semicolon) myFile.ts[1, 14]: Missing semicolon", + "sample": "ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon", "consumer": "human" }, { diff --git a/docs/formatters/pmd/index.html b/docs/formatters/pmd/index.html index 6ebb32c7f4c..1b7a53b8d21 100644 --- a/docs/formatters/pmd/index.html +++ b/docs/formatters/pmd/index.html @@ -6,7 +6,7 @@ - + consumer: machine diff --git a/docs/formatters/prose/index.html b/docs/formatters/prose/index.html index 054e16dd3dd..f01f4afd6f7 100644 --- a/docs/formatters/prose/index.html +++ b/docs/formatters/prose/index.html @@ -1,7 +1,7 @@ --- formatterName: prose description: The default formatter which outputs simple human-readable messages. -sample: 'myFile.ts[1, 14]: Missing semicolon' +sample: 'ERROR: myFile.ts[1, 14]: Missing semicolon' consumer: human layout: formatter title: 'Formatter: prose' diff --git a/docs/formatters/verbose/index.html b/docs/formatters/verbose/index.html index ad2fd1d4c29..5a7de802b60 100644 --- a/docs/formatters/verbose/index.html +++ b/docs/formatters/verbose/index.html @@ -2,7 +2,7 @@ formatterName: verbose description: The human-readable formatter which includes the rule name in messages. descriptionDetails: The output is the same as the prose formatter with the rule name included -sample: '(semicolon) myFile.ts[1, 14]: Missing semicolon' +sample: 'ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon' consumer: human layout: formatter title: 'Formatter: verbose' diff --git a/src/configuration.ts b/src/configuration.ts index 8e48c507e47..2039dea15f5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -21,7 +21,7 @@ import * as path from "path"; import * as resolve from "resolve"; import { FatalError } from "./error"; -import {arrayify, objectify, stripComments} from "./utils"; +import { arrayify, objectify, stripComments } from "./utils"; export interface IConfigurationFile { extends?: string | string[]; @@ -39,11 +39,9 @@ export interface IConfigurationLoadResult { } export const CONFIG_FILENAME = "tslint.json"; -/* tslint:disable:object-literal-key-quotes */ export const DEFAULT_CONFIG = { - "extends": "tslint:recommended", + extends: "tslint:recommended", }; -/* tslint:enable:object-literal-key-quotes */ const BUILT_IN_CONFIG = /^tslint:(.*)$/; @@ -244,3 +242,30 @@ export function getRulesDirectories(directories?: string | string[], relativeTo? return rulesDirectories; } + +export function isRuleEnabled(ruleConfigValue: any): boolean { + if (typeof ruleConfigValue === "boolean") { + return ruleConfigValue; + } + + if (Array.isArray(ruleConfigValue) && ruleConfigValue.length > 0) { + return ruleConfigValue[0]; + } + + if (ruleConfigValue.severity !== "off" && ruleConfigValue.severity !== "none") { + return true; + } + + return false; +} + +export function getRuleSeverity(ruleConfigValue: any) { + if (ruleConfigValue.severity && + (ruleConfigValue.severity.toLowerCase() === "warn" || + ruleConfigValue.severity.toLowerCase() === "warning")) { + + return "warning"; + } + + return "error"; +} diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index 7f6cb0279e9..ac585208ec5 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -18,7 +18,7 @@ import * as utils from "tsutils"; import * as ts from "typescript"; -import {AbstractRule} from "./language/rule/abstractRule"; +import {isRuleEnabled} from "./configuration"; import {IEnableDisablePosition} from "./ruleLoader"; export class EnableDisableRulesWalker { @@ -30,7 +30,7 @@ export class EnableDisableRulesWalker { this.enabledRules = []; if (rules) { for (const rule of Object.keys(rules)) { - if (AbstractRule.isRuleEnabled(rules[rule])) { + if (isRuleEnabled(rules[rule])) { this.enabledRules.push(rule); this.enableDisableRuleMap[rule] = [{ isEnabled: true, diff --git a/src/formatters/checkstyleFormatter.ts b/src/formatters/checkstyleFormatter.ts index c38405391b6..5d8912ebc55 100644 --- a/src/formatters/checkstyleFormatter.ts +++ b/src/formatters/checkstyleFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; import * as Utils from "../utils"; @@ -48,6 +48,7 @@ export class Formatter extends AbstractFormatter { }); let previousFilename: string | null = null; for (const failure of failuresSorted) { + const severity = failure.getRuleSeverity(); if (failure.getFileName() !== previousFilename) { if (previousFilename) { output += ""; @@ -57,7 +58,7 @@ export class Formatter extends AbstractFormatter { } output += "dotdot output += "source=\"failure.tslint." + this.escapeXml(failure.getRuleName()) + "\" />"; diff --git a/src/formatters/msbuildFormatter.ts b/src/formatters/msbuildFormatter.ts index 8af372d9602..0afb73216a8 100644 --- a/src/formatters/msbuildFormatter.ts +++ b/src/formatters/msbuildFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; import {camelize, dedent} from "../utils"; @@ -42,8 +42,9 @@ export class Formatter extends AbstractFormatter { const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); const positionTuple = `(${lineAndCharacter.line + 1},${lineAndCharacter.character + 1})`; + const severity = failure.getRuleSeverity(); - return `${fileName}${positionTuple}: warning ${camelizedRule}: ${failureString}`; + return `${fileName}${positionTuple}: ${severity} ${camelizedRule}: ${failureString}`; }); return outputLines.join("\n") + "\n"; diff --git a/src/formatters/pmdFormatter.ts b/src/formatters/pmdFormatter.ts index 1caa3595502..fe475df7bca 100644 --- a/src/formatters/pmdFormatter.ts +++ b/src/formatters/pmdFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; import * as Utils from "../utils"; @@ -30,7 +30,7 @@ export class Formatter extends AbstractFormatter { sample: Utils.dedent` - + `, consumer: "machine", @@ -49,11 +49,12 @@ export class Formatter extends AbstractFormatter { .replace(/"/g, """); const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); + const priority = failure.getRuleSeverity() === "warning" ? 4 : 3; output += " "; } diff --git a/src/formatters/proseFormatter.ts b/src/formatters/proseFormatter.ts index 997412c388f..bc750aca49a 100644 --- a/src/formatters/proseFormatter.ts +++ b/src/formatters/proseFormatter.ts @@ -24,14 +24,14 @@ export class Formatter extends AbstractFormatter { public static metadata: IFormatterMetadata = { formatterName: "prose", description: "The default formatter which outputs simple human-readable messages.", - sample: "myFile.ts[1, 14]: Missing semicolon", + sample: "ERROR: myFile.ts[1, 14]: Missing semicolon", consumer: "human", }; /* tslint:enable:object-literal-sort-keys */ public format(failures: RuleFailure[], fixes?: RuleFailure[]): string { if (failures.length === 0 && (!fixes || fixes.length === 0)) { - return ""; + return "\n"; } const fixLines: string[] = []; @@ -54,7 +54,7 @@ export class Formatter extends AbstractFormatter { const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); const positionTuple = `[${lineAndCharacter.line + 1}, ${lineAndCharacter.character + 1}]`; - return `${fileName}${positionTuple}: ${failureString}`; + return `${failure.getRuleSeverity().toUpperCase()}: ${fileName}${positionTuple}: ${failureString}`; }); return fixLines.concat(errorLines).join("\n") + "\n"; diff --git a/src/formatters/stylishFormatter.ts b/src/formatters/stylishFormatter.ts index 771fcb61cb6..36a240dd0f5 100644 --- a/src/formatters/stylishFormatter.ts +++ b/src/formatters/stylishFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; import * as colors from "colors"; @@ -39,10 +39,20 @@ export class Formatter extends AbstractFormatter { /* tslint:enable:object-literal-sort-keys */ public format(failures: RuleFailure[]): string { - if (typeof failures[0] === "undefined") { - return "\n"; + const outputLines = this.mapToMessages(failures); + + // Removes initial blank line + if (outputLines[0] === "") { + outputLines.shift(); } + return outputLines.join("\n") + "\n"; + } + + private mapToMessages(failures: RuleFailure[]): string[] { + if (!failures) { + return []; + } const outputLines: string[] = []; const positionMaxSize = this.getPositionMaxSize(failures); const ruleMaxSize = this.getRuleMaxSize(failures); @@ -71,21 +81,20 @@ export class Formatter extends AbstractFormatter { const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); let positionTuple = `${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`; - positionTuple = this.pad(positionTuple, positionMaxSize); - positionTuple = colors.red(positionTuple); + positionTuple = this.pad(positionTuple, positionMaxSize); - // Ouput + if (failure.getRuleSeverity() === "warning") { + positionTuple = colors.blue(failure.getRuleSeverity().toUpperCase() + ": " + positionTuple); + } else { + positionTuple = colors.red(failure.getRuleSeverity().toUpperCase() + ": " + positionTuple); + } + + // Output const output = `${positionTuple} ${ruleName} ${failureString}`; outputLines.push(output); } - - // Removes initial blank line - if (outputLines[0] === "") { - outputLines.shift(); - } - - return outputLines.join("\n") + "\n\n"; + return outputLines; } private pad(str: string, len: number): string { diff --git a/src/formatters/verboseFormatter.ts b/src/formatters/verboseFormatter.ts index fad23a9fad0..2ea696f64af 100644 --- a/src/formatters/verboseFormatter.ts +++ b/src/formatters/verboseFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; export class Formatter extends AbstractFormatter { /* tslint:disable:object-literal-sort-keys */ @@ -25,13 +25,19 @@ export class Formatter extends AbstractFormatter { formatterName: "verbose", description: "The human-readable formatter which includes the rule name in messages.", descriptionDetails: "The output is the same as the prose formatter with the rule name included", - sample: "(semicolon) myFile.ts[1, 14]: Missing semicolon", + sample: "ERROR: (semicolon) myFile.ts[1, 14]: Missing semicolon", consumer: "human", }; /* tslint:enable:object-literal-sort-keys */ public format(failures: RuleFailure[]): string { - const outputLines = failures.map((failure: RuleFailure) => { + + return this.mapToMessages(failures) + .join("\n") + "\n"; + } + + private mapToMessages(failures: RuleFailure[]): string[] { + return failures.map((failure: RuleFailure) => { const fileName = failure.getFileName(); const failureString = failure.getFailure(); const ruleName = failure.getRuleName(); @@ -39,9 +45,8 @@ export class Formatter extends AbstractFormatter { const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); const positionTuple = "[" + (lineAndCharacter.line + 1) + ", " + (lineAndCharacter.character + 1) + "]"; - return `(${ruleName}) ${fileName}${positionTuple}: ${failureString}`; + return `${failure.getRuleSeverity().toUpperCase()}: (${ruleName}) ${fileName}${positionTuple}: ${failureString}`; }); - return outputLines.join("\n") + "\n"; } } diff --git a/src/formatters/vsoFormatter.ts b/src/formatters/vsoFormatter.ts index d7763f5b7b0..90949496be4 100644 --- a/src/formatters/vsoFormatter.ts +++ b/src/formatters/vsoFormatter.ts @@ -34,8 +34,10 @@ export class Formatter extends AbstractFormatter { }; /* tslint:enable:object-literal-sort-keys */ - public format(failures: RuleFailure[]): string { - const outputLines = failures.map((failure: RuleFailure) => { + public format(failures: RuleFailure[], warnings: RuleFailure[] = []): string { + const all = failures.concat(warnings); + + const outputLines = all.map((failure: RuleFailure) => { const fileName = failure.getFileName(); const failureString = failure.getFailure(); const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); diff --git a/src/index.ts b/src/index.ts index 835ac72fc15..df8cd5c934f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,7 +35,8 @@ export * from "./language/walker"; export * from "./language/formatter/formatter"; export interface LintResult { - failureCount: number; + errorCount: number; + warningCount: number; failures: RuleFailure[]; fixes?: RuleFailure[]; format: string | FormatterFunction; diff --git a/src/language/formatter/formatter.ts b/src/language/formatter/formatter.ts index 7801a9a510b..ff3cecbf2d2 100644 --- a/src/language/formatter/formatter.ts +++ b/src/language/formatter/formatter.ts @@ -49,8 +49,8 @@ export type ConsumerType = "human" | "machine"; export interface IFormatter { /** * Formats linter results - * @param {RuleFailure[]} failures Linter errors that were not fixed - * @param {RuleFailure[]} fixes Fixed linter errors. Available when the `--fix` argument is used on the command line + * @param {RuleFailure[]} failures Linter failures that were not fixed + * @param {RuleFailure[]} fixes Fixed linter failures. Available when the `--fix` argument is used on the command line */ format(failures: RuleFailure[], fixes?: RuleFailure[]): string; } diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 3a051a6ecbd..33bff73978e 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -17,25 +17,16 @@ import * as ts from "typescript"; +import { getRuleSeverity, isRuleEnabled } from "../../configuration"; +import {arrayify} from "../../utils"; import {doesIntersect} from "../utils"; import {IWalker, WalkContext} from "../walker"; -import {IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure} from "./rule"; +import { IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure, RuleSeverity } from "./rule"; export abstract class AbstractRule implements IRule { public static metadata: IRuleMetadata; protected readonly ruleArguments: any[]; - - public static isRuleEnabled(ruleConfigValue: any): boolean { - if (typeof ruleConfigValue === "boolean") { - return ruleConfigValue; - } - - if (Array.isArray(ruleConfigValue) && ruleConfigValue.length > 0) { - return ruleConfigValue[0]; - } - - return false; - } + protected readonly ruleSeverity: RuleSeverity; constructor(public readonly ruleName: string, private value: any, private disabledIntervals: IDisabledInterval[]) { if (Array.isArray(value) && value.length > 1) { @@ -43,6 +34,12 @@ export abstract class AbstractRule implements IRule { } else { this.ruleArguments = []; } + + if (value.options) { + this.ruleArguments = arrayify(value.options); + } + + this.ruleSeverity = getRuleSeverity(value); } public getOptions(): IOptions { @@ -50,6 +47,7 @@ export abstract class AbstractRule implements IRule { disabledIntervals: this.disabledIntervals, ruleArguments: this.ruleArguments, ruleName: this.ruleName, + ruleSeverity: this.ruleSeverity, }; } @@ -61,7 +59,7 @@ export abstract class AbstractRule implements IRule { } public isEnabled(): boolean { - return AbstractRule.isRuleEnabled(this.value); + return isRuleEnabled(this.value); } protected applyWithFunction(sourceFile: ts.SourceFile, walkFn: (ctx: WalkContext) => void): RuleFailure[]; diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 26ec45b85a3..f9b2637baf3 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -86,8 +86,12 @@ export interface IRuleMetadata { export type RuleType = "functionality" | "maintainability" | "style" | "typescript"; +export type RuleSeverity = "warning" | "error"; + export interface IOptions { ruleArguments: any[]; + ruleSeverity: RuleSeverity; + ruleName: string; disabledIntervals: IDisabledInterval[]; } @@ -109,6 +113,7 @@ export interface IRuleFailureJson { failure: string; fix?: Fix; name: string; + ruleSeverity: string; ruleName: string; startPosition: IRuleFailurePositionJson; } @@ -227,6 +232,7 @@ export class RuleFailure { private startPosition: RuleFailurePosition; private endPosition: RuleFailurePosition; private rawLines: string; + private ruleSeverity: RuleSeverity; constructor(private sourceFile: ts.SourceFile, start: number, @@ -239,6 +245,7 @@ export class RuleFailure { this.startPosition = this.createFailurePosition(start); this.endPosition = this.createFailurePosition(end); this.rawLines = sourceFile.text; + this.ruleSeverity = "warning"; } public getFileName() { @@ -273,6 +280,14 @@ export class RuleFailure { return this.rawLines; } + public getRuleSeverity() { + return this.ruleSeverity; + } + + public setRuleSeverity(value: RuleSeverity) { + this.ruleSeverity = value; + } + public toJson(): IRuleFailureJson { return { endPosition: this.endPosition.toJson(), @@ -280,6 +295,7 @@ export class RuleFailure { fix: this.fix, name: this.fileName, ruleName: this.ruleName, + ruleSeverity: this.ruleSeverity.toUpperCase(), startPosition: this.startPosition.toJson(), }; } diff --git a/src/language/walker/walkContext.ts b/src/language/walker/walkContext.ts index 3af20a74c4b..f93b480c192 100644 --- a/src/language/walker/walkContext.ts +++ b/src/language/walker/walkContext.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import {Fix, Replacement, RuleFailure} from "../rule/rule"; +import { Fix, Replacement, RuleFailure } from "../rule/rule"; export class WalkContext { public readonly failures: RuleFailure[] = []; diff --git a/src/linter.ts b/src/linter.ts index 4e947e8f734..dc725a833ce 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -34,7 +34,7 @@ import { findFormatter } from "./formatterLoader"; import { ILinterOptions, LintResult } from "./index"; import { IFormatter } from "./language/formatter/formatter"; import { createLanguageService, wrapProgram } from "./language/languageServiceHost"; -import { Fix, IRule, RuleFailure } from "./language/rule/rule"; +import { Fix, IRule, RuleFailure, RuleSeverity } from "./language/rule/rule"; import { TypedRule } from "./language/rule/typedRule"; import * as utils from "./language/utils"; import { loadRules } from "./ruleLoader"; @@ -136,6 +136,18 @@ class Linter { } } this.failures = this.failures.concat(fileFailures); + + // add rule severity to failures + const ruleSeverityMap = new Map(enabledRules.map((rule) => { + return [rule.getOptions().ruleName, rule.getOptions().ruleSeverity] as [string, RuleSeverity]; + })); + for (const failure of this.failures) { + const severity = ruleSeverityMap.get(failure.getRuleName()); + if (severity === undefined) { + throw new Error(`Severity for rule '${failure.getRuleName()} not found`); + } + failure.setRuleSeverity(severity); + } } public getResult(): LintResult { @@ -152,12 +164,14 @@ class Linter { const output = formatter.format(this.failures, this.fixes); + const errorCount = this.failures.filter((failure) => failure.getRuleSeverity() === "error").length; return { - failureCount: this.failures.length, + errorCount, failures: this.failures, fixes: this.fixes, format: formatterName, output, + warningCount: this.failures.length - errorCount, }; } diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index f95f728a499..4ace0397ab8 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -18,7 +18,7 @@ import * as fs from "fs"; import * as path from "path"; -import { getRelativePath } from "./configuration"; +import { getRelativePath, isRuleEnabled } from "./configuration"; import { showWarningOnce } from "./error"; import { AbstractRule } from "./language/rule/abstractRule"; import { IDisabledInterval, IRule } from "./language/rule/rule"; @@ -44,7 +44,7 @@ export function loadRules(ruleConfiguration: {[name: string]: any}, for (const ruleName in ruleConfiguration) { if (ruleConfiguration.hasOwnProperty(ruleName)) { const ruleValue = ruleConfiguration[ruleName]; - if (AbstractRule.isRuleEnabled(ruleValue) || enableDisableRuleMap.hasOwnProperty(ruleName)) { + if (isRuleEnabled(ruleValue) || enableDisableRuleMap.hasOwnProperty(ruleName)) { const Rule: (typeof AbstractRule) | null = findRule(ruleName, rulesDirectories); if (Rule == null) { notFoundRules.push(ruleName); diff --git a/src/rules/eoflineRule.ts b/src/rules/eoflineRule.ts index 2442b943b0e..9331c9ae4a1 100644 --- a/src/rules/eoflineRule.ts +++ b/src/rules/eoflineRule.ts @@ -42,7 +42,8 @@ export class Rule extends Lint.Rules.AbstractRule { } return this.filterFailures([ - new Lint.RuleFailure(sourceFile, length, length, Rule.FAILURE_STRING, this.getOptions().ruleName), + new Lint.RuleFailure(sourceFile, length, length, Rule.FAILURE_STRING, + this.getOptions().ruleName), ]); } } diff --git a/src/rules/maxFileLineCountRule.ts b/src/rules/maxFileLineCountRule.ts index 2b3f8905354..0ae00b1abfe 100644 --- a/src/rules/maxFileLineCountRule.ts +++ b/src/rules/maxFileLineCountRule.ts @@ -24,7 +24,7 @@ export class Rule extends Lint.Rules.AbstractRule { ruleName: "max-file-line-count", description: "Requires files to remain under a certain number of lines", rationale: Lint.Utils.dedent` - Limiting the number of lines allowed in a file allows files to remain small, + Limiting the number of lines allowed in a file allows files to remain small, single purpose, and maintainable.`, optionsDescription: "An integer indicating the maximum number of lines.", options: { @@ -63,7 +63,8 @@ export class Rule extends Lint.Rules.AbstractRule { if (lineCount > lineLimit && disabledIntervals.length === 0) { const errorString = Rule.FAILURE_STRING_FACTORY(lineCount, lineLimit); - ruleFailures.push(new Lint.RuleFailure(sourceFile, 0, 1, errorString, this.getOptions().ruleName)); + ruleFailures.push(new Lint.RuleFailure(sourceFile, 0, 1, errorString, + this.getOptions().ruleName)); } return ruleFailures; } diff --git a/src/rules/noImportSideEffectRule.ts b/src/rules/noImportSideEffectRule.ts index b5e628325dc..899d77ae790 100644 --- a/src/rules/noImportSideEffectRule.ts +++ b/src/rules/noImportSideEffectRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "tslint"; +import * as Lint from "../index"; const OPTION_IGNORE_MODULE = "ignore-module"; diff --git a/src/runner.ts b/src/runner.ts index af8c1c24a58..deaba9a0c33 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -251,10 +251,10 @@ export class Runner { const lintResult = linter.getResult(); this.outputStream.write(lintResult.output, () => { - if (lintResult.failureCount > 0) { - onComplete(this.options.force ? 0 : 2); - } else { + if (this.options.force || lintResult.errorCount === 0) { onComplete(0); + } else { + onComplete(2); } }); diff --git a/test/config/tslint-custom-rules-with-dir.json b/test/config/tslint-custom-rules-with-dir.json index 8c4ae31b1e5..6467b260767 100644 --- a/test/config/tslint-custom-rules-with-dir.json +++ b/test/config/tslint-custom-rules-with-dir.json @@ -1,9 +1,13 @@ { "rulesDirectory": "../files/custom-rules/", "jsRules": { - "always-fail": true + "always-fail": { + "severity": "error" + } }, "rules": { - "always-fail": true + "always-fail": { + "severity": "error" + } } } diff --git a/test/config/tslint-custom-rules-with-two-dirs.json b/test/config/tslint-custom-rules-with-two-dirs.json index a43cf3db773..76f2508e997 100644 --- a/test/config/tslint-custom-rules-with-two-dirs.json +++ b/test/config/tslint-custom-rules-with-two-dirs.json @@ -1,13 +1,25 @@ { "rulesDirectory": ["../files/custom-rules-2", "../files/custom-rules/"], "jsRules": { - "always-fail": true, - "no-fail": true, - "rule-two": true + "always-fail": { + "severity": "error" + }, + "no-fail": { + "severity": "error" + }, + "rule-two": { + "severity": "error" + } }, "rules": { - "always-fail": true, - "no-fail": true, - "rule-two": true + "always-fail": { + "severity": "error" + }, + "no-fail": { + "severity": "error" + }, + "rule-two": { + "severity": "error" + } } } diff --git a/test/config/tslint-custom-rules.json b/test/config/tslint-custom-rules.json index 250eafcd974..ce5d5273980 100644 --- a/test/config/tslint-custom-rules.json +++ b/test/config/tslint-custom-rules.json @@ -1,8 +1,12 @@ { "jsRules": { - "always-fail": true + "always-fail": { + "severity": "error" + } }, "rules": { - "always-fail": true + "always-fail": { + "severity": "error" + } } } diff --git a/test/config/tslint-extends-builtin.json b/test/config/tslint-extends-builtin.json index 4daad33a5c8..690e68c4ebb 100644 --- a/test/config/tslint-extends-builtin.json +++ b/test/config/tslint-extends-builtin.json @@ -1,9 +1,13 @@ { "extends": "tslint:latest", "jsRules": { - "no-eval": false + "no-eval": { + "severity": "none" + } }, "rules": { - "no-eval": false + "no-eval": { + "severity": "none" + } } } diff --git a/test/config/tslint-extends-package-array.json b/test/config/tslint-extends-package-array.json index e52fb098a1d..31b053a7a79 100644 --- a/test/config/tslint-extends-package-array.json +++ b/test/config/tslint-extends-package-array.json @@ -4,9 +4,13 @@ "./tslint-custom-rules-with-two-dirs.json" ], "jsRules": { - "always-fail": false + "always-fail": { + "severity": "none" + } }, "rules": { - "always-fail": false + "always-fail": { + "severity": "none" + } } } diff --git a/test/config/tslint-extends-package-boolean.json b/test/config/tslint-extends-package-boolean.json new file mode 100644 index 00000000000..ed5eced16c4 --- /dev/null +++ b/test/config/tslint-extends-package-boolean.json @@ -0,0 +1,11 @@ +{ + "extends": "tslint-test-custom-rules", + "jsRules": { + "rule-two": true, + "rule-three": false + }, + "rules": { + "rule-two": true, + "rule-three": false + } +} diff --git a/test/config/tslint-extends-package-two-levels.json b/test/config/tslint-extends-package-two-levels.json index 2e2a12b63ac..2afc4f1cd0a 100644 --- a/test/config/tslint-extends-package-two-levels.json +++ b/test/config/tslint-extends-package-two-levels.json @@ -2,9 +2,13 @@ "extends": "tslint-test-config/tslint.json", "rulesDirectory": "../files/custom-rules", "jsRules": { - "always-fail": false + "always-fail": { + "severity": "none" + } }, "rules": { - "always-fail": false + "always-fail": { + "severity": "none" + } } } diff --git a/test/config/tslint-extends-package.json b/test/config/tslint-extends-package.json index ed5eced16c4..8be210769c4 100644 --- a/test/config/tslint-extends-package.json +++ b/test/config/tslint-extends-package.json @@ -1,11 +1,19 @@ { "extends": "tslint-test-custom-rules", "jsRules": { - "rule-two": true, - "rule-three": false + "rule-two": { + "severity": "error" + }, + "rule-three": { + "severity": "none" + } }, "rules": { - "rule-two": true, - "rule-three": false + "rule-two": { + "severity": "error" + }, + "rule-three": { + "severity": "none" + } } } diff --git a/test/config/tslint-extends-relative.json b/test/config/tslint-extends-relative.json index c53505e38af..d9cecc98f78 100644 --- a/test/config/tslint-extends-relative.json +++ b/test/config/tslint-extends-relative.json @@ -1,9 +1,13 @@ { "extends": "./tslint-custom-rules-with-two-dirs.json", "jsRules": { - "always-fail": false + "always-fail": { + "severity": "none" + } }, "rules": { - "always-fail": false + "always-fail": { + "severity": "none" + } } } diff --git a/test/config/tslint-with-comments.json b/test/config/tslint-with-comments.json index ef12ab77da1..157a5f63515 100644 --- a/test/config/tslint-with-comments.json +++ b/test/config/tslint-with-comments.json @@ -4,7 +4,9 @@ /* "rule-one": true, */ - "rule-two": true, + "rule-two": { + "severity": "error" // after comment + }, "rule-three": "//not a comment", "rule-four": "/*also not a comment*/" }, @@ -13,7 +15,10 @@ /* "rule-one": true, */ - "rule-two": true, + "rule-two": { + "severity": "error" + // after comment + }, "rule-three": "//not a comment", "rule-four": "/*also not a comment*/" } diff --git a/test/config/tslint-with-jsrules.json b/test/config/tslint-with-jsrules.json index dc4f5463b1c..5ce2f002bfd 100644 --- a/test/config/tslint-with-jsrules.json +++ b/test/config/tslint-with-jsrules.json @@ -1,5 +1,7 @@ { "jsRules": { - "rule": true + "rule": { + "severity": "error" + } } } \ No newline at end of file diff --git a/test/configurationTests.ts b/test/configurationTests.ts index 819bf5871c2..e2c716e16cf 100644 --- a/test/configurationTests.ts +++ b/test/configurationTests.ts @@ -81,15 +81,41 @@ describe("Configuration", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json"); assert.isArray(config.rulesDirectory); - assert.isTrue(config.rules["no-fail"], "did not pick up 'no-fail' in base config"); - assert.isFalse(config.rules["always-fail"], "did not set 'always-fail' in top config"); - assert.isTrue(config.jsRules["no-fail"]); - assert.isFalse(config.jsRules["always-fail"]); + assert.equal("error", config.rules["no-fail"].severity, "did not pick up 'no-fail' in base config"); + assert.equal("none", config.rules["always-fail"].severity, "did not set 'always-fail' in top config"); + assert.equal("error", config.jsRules["no-fail"].severity); + assert.equal("none", config.jsRules["always-fail"].severity); }); it("extends with package", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package.json"); + assert.isArray(config.rulesDirectory); + /* tslint:disable:object-literal-sort-keys */ + assert.deepEqual(config.jsRules, { + "rule-one": true, + "rule-three": { + severity: "none", + }, + "rule-two": { + severity: "error", + }, + }); + assert.deepEqual(config.rules, { + "rule-one": true, + "rule-three": { + severity: "none", + }, + "rule-two": { + severity: "error", + }, + }); + /* tslint:enable:object-literal-sort-keys */ + }); + + it("extends with package - boolean configuration", () => { + const config = loadConfigurationFromPath("./test/config/tslint-extends-package-boolean.json"); + assert.isArray(config.rulesDirectory); /* tslint:disable:object-literal-sort-keys */ assert.deepEqual(config.jsRules, { @@ -122,9 +148,9 @@ describe("Configuration", () => { it("extends with builtin", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-builtin.json"); assert.isUndefined(config.jsRules["no-var-keyword"]); - assert.isFalse(config.jsRules["no-eval"]); + assert.equal("none", config.jsRules["no-eval"].severity); assert.isTrue(config.rules["no-var-keyword"]); - assert.isFalse(config.rules["no-eval"]); + assert.equal("none", config.rules["no-eval"].severity); }); describe("with config not relative to tslint", () => { @@ -158,13 +184,17 @@ describe("Configuration", () => { assert.isTrue(fs.existsSync(config.rulesDirectory![1])); /* tslint:disable:object-literal-sort-keys */ assert.deepEqual(config.jsRules, { - "always-fail": false, + "always-fail": { + severity: "none", + }, "rule-one": true, "rule-two": true, "rule-four": true, }); assert.deepEqual(config.rules, { - "always-fail": false, + "always-fail": { + severity: "none", + }, "rule-one": true, "rule-two": true, "rule-four": true, @@ -177,16 +207,28 @@ describe("Configuration", () => { assert.isArray(config.rulesDirectory); assert.deepEqual(config.jsRules, { - "always-fail": false, - "no-fail": true, + "always-fail": { + severity: "none", + }, + "no-fail": { + severity: "error", + }, "rule-one": true, - "rule-two": true, + "rule-two": { + severity: "error", + }, }); assert.deepEqual(config.rules, { - "always-fail": false, - "no-fail": true, + "always-fail": { + severity: "none", + }, + "no-fail": { + severity: "error", + }, "rule-one": true, - "rule-two": true, + "rule-two": { + severity: "error", + }, }); }); @@ -195,12 +237,16 @@ describe("Configuration", () => { /* tslint:disable:object-literal-sort-keys */ assert.deepEqual(config.jsRules, { - "rule-two": true, + "rule-two": { + severity: "error", + }, "rule-three": "//not a comment", "rule-four": "/*also not a comment*/", }); assert.deepEqual(config.rules, { - "rule-two": true, + "rule-two": { + severity: "error", + }, "rule-three": "//not a comment", "rule-four": "/*also not a comment*/", }); diff --git a/test/files/custom-rules/alwaysFailRule.js b/test/files/custom-rules/alwaysFailRule.js index b6d3d25921c..4f1b0bf7f86 100644 --- a/test/files/custom-rules/alwaysFailRule.js +++ b/test/files/custom-rules/alwaysFailRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint"); +var Lint = require('../../../lib/index'); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/formatters/checkstyleFormatterTests.ts b/test/formatters/checkstyleFormatterTests.ts index d59749a19a9..be3a9a9cd52 100644 --- a/test/formatters/checkstyleFormatterTests.ts +++ b/test/formatters/checkstyleFormatterTests.ts @@ -17,7 +17,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("Checkstyle Formatter", () => { const TEST_FILE_1 = "formatters/jsonFormatter.test.ts"; // reuse existing sample file @@ -38,23 +39,23 @@ describe("Checkstyle Formatter", () => { const maxPosition2 = sourceFile2.getFullWidth(); const failures = [ - new RuleFailure(sourceFile1, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile1, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile1, maxPosition1 - 1, maxPosition1, "last failure", "last-name"), - new RuleFailure(sourceFile2, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile2, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile2, maxPosition2 - 1, maxPosition2, "last failure", "last-name"), + createFailure(sourceFile1, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile1, 2, 3, "&<>'\" should be escaped", "escape", undefined, "error"), + createFailure(sourceFile1, maxPosition1 - 1, maxPosition1, "last failure", "last-name", undefined, "error"), + createFailure(sourceFile2, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile2, 2, 3, "&<>'\" should be escaped", "escape", undefined, "warning"), + createFailure(sourceFile2, maxPosition2 - 1, maxPosition2, "last failure", "last-name", undefined, "warning"), ]; const expectedResult = '' + `` + - '' + - '' + + '' + - '' + + '' + "" + `` + - '' + + '' + '' + '' + diff --git a/test/formatters/codeFrameFormatterTests.ts b/test/formatters/codeFrameFormatterTests.ts index fb2e6820244..38f43db9d64 100644 --- a/test/formatters/codeFrameFormatterTests.ts +++ b/test/formatters/codeFrameFormatterTests.ts @@ -18,7 +18,8 @@ import * as colors from "colors"; import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import {IFormatter, TestUtils} from "../lint"; +import { createFailure } from "./utils"; describe("CodeFrame Formatter", () => { const TEST_FILE = "formatters/codeFrameFormatter.test.ts"; @@ -36,10 +37,10 @@ describe("CodeFrame Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), - new RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), + createFailure(sourceFile, 0, maxPosition, "full failure", "full-name", undefined, "error"), ]; const expectedResultPlain = diff --git a/test/formatters/externalFormatterTests.ts b/test/formatters/externalFormatterTests.ts index 12733cf73e5..e99b5ff8292 100644 --- a/test/formatters/externalFormatterTests.ts +++ b/test/formatters/externalFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("External Formatter", () => { const TEST_FILE = "formatters/externalFormatter.test.ts"; @@ -33,9 +34,9 @@ describe("External Formatter", () => { it("formats failures", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), ]; const expectedResult = diff --git a/test/formatters/fileslistFormatterTests.ts b/test/formatters/fileslistFormatterTests.ts index e77e444afe6..b2ec3304bda 100644 --- a/test/formatters/fileslistFormatterTests.ts +++ b/test/formatters/fileslistFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("Files-list Formatter", () => { const TEST_FILE = "formatters/fileslistFormatter.test.ts"; @@ -32,8 +33,8 @@ describe("Files-list Formatter", () => { it("formats failures", () => { // this part really doesn't matter, as long as we get some failure` const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "last failure", "last-name", undefined, "error"), ]; // we only print file-names in this formatter diff --git a/test/formatters/jsonFormatterTests.ts b/test/formatters/jsonFormatterTests.ts index 1986e61572e..d485e22ec01 100644 --- a/test/formatters/jsonFormatterTests.ts +++ b/test/formatters/jsonFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {Fix, IFormatter, Replacement, RuleFailure, TestUtils} from "../lint"; +import { Fix, IFormatter, Replacement, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("JSON Formatter", () => { const TEST_FILE = "formatters/jsonFormatter.test.ts"; @@ -33,12 +34,13 @@ describe("JSON Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), - new RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name", + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), + createFailure(sourceFile, 0, maxPosition, "full failure", "full-name", new Fix("full-name", [ new Replacement(0, 0, ""), - ])), + ]), + "error"), ]; /* tslint:disable:object-literal-sort-keys */ @@ -56,6 +58,7 @@ describe("JSON Formatter", () => { character: 1, }, ruleName: "first-name", + ruleSeverity: "ERROR", }, { name: TEST_FILE, @@ -71,6 +74,7 @@ describe("JSON Formatter", () => { character: 0, }, ruleName: "last-name", + ruleSeverity: "ERROR", }, { name: TEST_FILE, @@ -96,6 +100,7 @@ describe("JSON Formatter", () => { character: 0, }, ruleName: "full-name", + ruleSeverity: "ERROR", }]; /* tslint:enable:object-literal-sort-keys */ diff --git a/test/formatters/msbuildFormatterTests.ts b/test/formatters/msbuildFormatterTests.ts index ef2fd8f4344..8de8d2aff3c 100644 --- a/test/formatters/msbuildFormatterTests.ts +++ b/test/formatters/msbuildFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("MSBuild Formatter", () => { const TEST_FILE = "formatters/msbuildFormatter.test.ts"; @@ -33,15 +34,15 @@ describe("MSBuild Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "warning"), ]; const expectedResult = - getFailureString(TEST_FILE, 1, 1, "first failure", "firstName") + - getFailureString(TEST_FILE, 2, 12, "mid failure", "midName") + - getFailureString(TEST_FILE, 9, 2, "last failure", "lastName"); + getFailureString(TEST_FILE, 1, 1, "first failure", "firstName", "error") + + getFailureString(TEST_FILE, 2, 12, "mid failure", "midName", "error") + + getFailureString(TEST_FILE, 9, 2, "last failure", "lastName", "warning"); const actualResult = formatter.format(failures); assert.equal(actualResult, expectedResult); @@ -52,7 +53,7 @@ describe("MSBuild Formatter", () => { assert.equal(result, "\n"); }); - function getFailureString(file: string, line: number, character: number, reason: string, ruleCamelCase: string) { - return `${file}(${line},${character}): warning ${ruleCamelCase}: ${reason}\n`; + function getFailureString(file: string, line: number, character: number, reason: string, ruleCamelCase: string, severity: string) { + return `${file}(${line},${character}): ${severity} ${ruleCamelCase}: ${reason}\n`; } }); diff --git a/test/formatters/pmdFormatterTests.ts b/test/formatters/pmdFormatterTests.ts index 2e95f243b39..eeae2d209ad 100644 --- a/test/formatters/pmdFormatterTests.ts +++ b/test/formatters/pmdFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("PMD Formatter", () => { const TEST_FILE = "formatters/pmdFormatter.test.ts"; @@ -33,27 +34,27 @@ describe("PMD Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), - new RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "warning"), + createFailure(sourceFile, 0, maxPosition, "full failure", "full-name", undefined, "warning"), ]; const expectedResult = "" + "" + - " " + + " " + "" + "" + "" + - " " + + " " + "" + "" + "" + - " " + + " " + "" + "" + "" + - " " + + " " + "" + "" + ""; diff --git a/test/formatters/proseFormatterTests.ts b/test/formatters/proseFormatterTests.ts index b83e550a6f1..fb5569b4c20 100644 --- a/test/formatters/proseFormatterTests.ts +++ b/test/formatters/proseFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("Prose Formatter", () => { const TEST_FILE = "formatters/proseFormatter.test.ts"; @@ -33,15 +34,15 @@ describe("Prose Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "warning"), ]; const expectedResult = - TEST_FILE + getPositionString(1, 1) + "first failure\n" + - TEST_FILE + getPositionString(2, 12) + "mid failure\n" + - TEST_FILE + getPositionString(9, 2) + "last failure\n"; + "ERROR: " + TEST_FILE + getPositionString(1, 1) + "first failure\n" + + "ERROR: " + TEST_FILE + getPositionString(2, 12) + "mid failure\n" + + "WARNING: " + TEST_FILE + getPositionString(9, 2) + "last failure\n"; const actualResult = formatter.format(failures); assert.equal(actualResult, expectedResult); @@ -49,21 +50,21 @@ describe("Prose Formatter", () => { it("formats fixes", () => { const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), ]; const mockFix = { getFileName: () => "file2" } as any; const fixes = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), mockFix, ]; const expectedResult = `Fixed 2 error(s) in ${TEST_FILE}\n` + `Fixed 1 error(s) in file2\n\n` + - `${TEST_FILE}${getPositionString(1, 1)}first failure\n`; + `ERROR: ${TEST_FILE}${getPositionString(1, 1)}first failure\n`; const actualResult = formatter.format(failures, fixes); assert.equal(actualResult, expectedResult); @@ -71,7 +72,7 @@ describe("Prose Formatter", () => { it("handles no failures", () => { const result = formatter.format([]); - assert.equal(result, ""); + assert.equal(result, "\n"); }); function getPositionString(line: number, character: number) { diff --git a/test/formatters/stylishFormatterTests.ts b/test/formatters/stylishFormatterTests.ts index a694ccf3e25..e20c777b749 100644 --- a/test/formatters/stylishFormatterTests.ts +++ b/test/formatters/stylishFormatterTests.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import * as colors from "colors"; - import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("Stylish Formatter", () => { const TEST_FILE = "formatters/stylishFormatter.test.ts"; @@ -35,29 +34,21 @@ describe("Stylish Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), - new RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), + createFailure(sourceFile, 0, maxPosition, "full failure", "full-name", undefined, "error"), ]; const maxPositionObj = sourceFile.getLineAndCharacterOfPosition(maxPosition - 1); const maxPositionTuple = `${maxPositionObj.line + 1}:${maxPositionObj.character + 1}`; - const expectedResult = colors.enabled ? - "formatters/stylishFormatter.test.ts" + "\n" + - "\u001b[31m1:1\u001b[39m \u001b[90mfirst-name\u001b[39m \u001b[33mfirst failure\u001b[39m" + "\n" + - "\u001b[31m1:3\u001b[39m \u001b[90mescape \u001b[39m \u001b[33m&<>'\" should be escaped\u001b[39m" + "\n" + - `\u001b[31m${maxPositionTuple}\u001b[39m \u001b[90mlast-name \u001b[39m \u001b[33mlast failure\u001b[39m` + "\n" + - "\u001b[31m1:1\u001b[39m \u001b[90mfull-name \u001b[39m \u001b[33mfull failure\u001b[39m" + "\n" + - "\n" : - "formatters/stylishFormatter.test.ts" + "\n" + - "1:1 first-name first failure" + "\n" + - "1:3 escape &<>\'\" should be escaped" + "\n" + - `${maxPositionTuple} last-name last failure` + "\n" + - "1:1 full-name full failure" + "\n" + - "\n"; + const expectedResult = "formatters/stylishFormatter.test.ts" + "\n" + + "\u001b[31mERROR: 1:1\u001b[39m \u001b[90mfirst-name\u001b[39m \u001b[33mfirst failure\u001b[39m" + "\n" + + "\u001b[31mERROR: 1:3\u001b[39m \u001b[90mescape \u001b[39m \u001b[33m&<>'\" should be escaped\u001b[39m" + "\n" + + `\u001b[31mERROR: ${maxPositionTuple}\u001b[39m \u001b[90mlast-name \u001b[39m \u001b[33mlast failure\u001b[39m` + "\n" + + "\u001b[31mERROR: 1:1\u001b[39m \u001b[90mfull-name \u001b[39m \u001b[33mfull failure\u001b[39m" + "\n"; assert.equal(formatter.format(failures), expectedResult); }); diff --git a/test/formatters/utils.ts b/test/formatters/utils.ts new file mode 100644 index 00000000000..afa9f4d1c15 --- /dev/null +++ b/test/formatters/utils.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2017 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. + */ +import * as ts from "typescript"; +import { Fix, RuleFailure, RuleSeverity } from "../../src/language/rule/rule"; + +export function createFailure(sourceFile: ts.SourceFile, + start: number, + end: number, + failure: string, + ruleName: string, + fix?: Fix, + ruleSeverity: RuleSeverity = "warning") { + + const rule = new RuleFailure(sourceFile, start, end, failure, ruleName, fix); + rule.setRuleSeverity(ruleSeverity); + return rule; +} diff --git a/test/formatters/verboseFormatterTests.ts b/test/formatters/verboseFormatterTests.ts index 2cb494fa845..dab9237d705 100644 --- a/test/formatters/verboseFormatterTests.ts +++ b/test/formatters/verboseFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("Verbose Formatter", () => { const TEST_FILE = "formatters/proseFormatter.test.ts"; @@ -33,15 +34,15 @@ describe("Verbose Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), ]; const expectedResult = - "(first-name) " + TEST_FILE + getPositionString(1, 1) + "first failure\n" + - "(mid-name) " + TEST_FILE + getPositionString(2, 12) + "mid failure\n" + - "(last-name) " + TEST_FILE + getPositionString(9, 2) + "last failure\n"; + "ERROR: (first-name) " + TEST_FILE + getPositionString(1, 1) + "first failure\n" + + "ERROR: (mid-name) " + TEST_FILE + getPositionString(2, 12) + "mid failure\n" + + "ERROR: (last-name) " + TEST_FILE + getPositionString(9, 2) + "last failure\n"; const actualResult = formatter.format(failures); assert.equal(actualResult, expectedResult); diff --git a/test/formatters/vsoFormatterTests.ts b/test/formatters/vsoFormatterTests.ts index 77d01d26371..3f34bbe01c6 100644 --- a/test/formatters/vsoFormatterTests.ts +++ b/test/formatters/vsoFormatterTests.ts @@ -16,7 +16,8 @@ import * as ts from "typescript"; -import {IFormatter, RuleFailure, TestUtils} from "../lint"; +import { IFormatter, TestUtils } from "../lint"; +import { createFailure } from "./utils"; describe("VSO Formatter", () => { const TEST_FILE = "formatters/vsoFormatter.test.ts"; @@ -33,9 +34,9 @@ describe("VSO Formatter", () => { const maxPosition = sourceFile.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), + createFailure(sourceFile, 32, 36, "mid failure", "mid-name", undefined, "error"), + createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), ]; const expectedResult = diff --git a/test/ruleLoaderTests.ts b/test/ruleLoaderTests.ts index 2c81f20b18f..4a2778189f0 100644 --- a/test/ruleLoaderTests.ts +++ b/test/ruleLoaderTests.ts @@ -50,6 +50,49 @@ describe("Rule Loader", () => { assert.equal(rules.length, 1); }); + it("properly sets rule severity with options", () => { + const withOptions = { + "callable-types": true, + "max-line-length": { + options: 140, + severity: "warning", + }, + }; + + const rules = loadRules(withOptions, {}, [builtRulesDir]); + assert.equal(rules.length, 2); + assert.equal(rules[0].getOptions().ruleSeverity, "error"); + assert.equal(rules[1].getOptions().ruleSeverity, "warning"); + }); + + it("properly sets rule severity with no options", () => { + const noOptions = { + "callable-types": true, + "interface-name": { + severity: "warning", + }, + }; + + const rules = loadRules(noOptions, {}, [builtRulesDir]); + assert.equal(rules.length, 2); + assert.equal(rules[0].getOptions().ruleSeverity, "error"); + assert.equal(rules[1].getOptions().ruleSeverity, "warning"); + }); + + it("properly sets rule severity with options but no severity", () => { + const noSeverity = { + "callable-types": true, + "max-line-length": { + options: 140, + }, + }; + + const rules = loadRules(noSeverity, {}, [builtRulesDir]); + assert.equal(rules.length, 2); + assert.equal(rules[0].getOptions().ruleSeverity, "error"); + assert.equal(rules[1].getOptions().ruleSeverity, "error"); + }); + it("loads disabled rules if rule in enableDisableRuleMap", () => { const validConfiguration: {[name: string]: any} = { "class-name": true, From 47e37576aa7d6542035b406860a2f1ff5dabaad3 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 1 Mar 2017 13:08:26 -0800 Subject: [PATCH 2/8] Remove .project/ folder from repo (#2276) --- .project | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .project diff --git a/.project b/.project deleted file mode 100644 index b4816234ede..00000000000 --- a/.project +++ /dev/null @@ -1,37 +0,0 @@ - - - tslint - - - - - - com.palantir.typescript.typeScriptBuilder - - - - - - com.palantir.typescript.typeScriptNature - - - - 1414432614134 - - 22 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-tslint.d.ts - - - - 1414431975439 - test - 10 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-true-false-files - - - - From 7fb522341110158669e8ddeab064790637d72c35 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 2 Mar 2017 08:45:35 -0800 Subject: [PATCH 3/8] Support dev versions of typescript (#2287) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57fba87e655..af275c8bd92 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "update-notifier": "^2.0.0" }, "peerDependencies": { - "typescript": ">=2.0.0" + "typescript": ">=2.0.0 || >=2.0.0-dev || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev" }, "devDependencies": { "@types/babel-code-frame": "^6.20.0", From 8c13b523077c365640e818473e5ba8b1ecc3f214 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 2 Mar 2017 13:38:36 -0500 Subject: [PATCH 4/8] Update circle.yml to test against typescript@next (#2288) --- circle.yml | 4 ++-- scripts/assertMinCircleNodes.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 4a2f85346de..363c0bcdcc7 100644 --- a/circle.yml +++ b/circle.yml @@ -5,12 +5,12 @@ general: dependencies: pre: - node ./scripts/assertMinCircleNodes.js $CIRCLE_NODE_TOTAL - - case $CIRCLE_NODE_INDEX in 0) nvm use 4.1.2 ;; 1) nvm use 5.7 ;; [2-3]) nvm use 6.1 ;; esac + - case $CIRCLE_NODE_INDEX in 0) nvm use 4.1.2 ;; 1) nvm use 5.7 ;; [2-4]) nvm use 6.1 ;; esac override: - yarn test: override: - - case $CIRCLE_NODE_INDEX in [0-2]) yarn verify ;; 3) npm run clean && yarn compile && yarn add typescript@2.0.10 && yarn test ;; esac: + - case $CIRCLE_NODE_INDEX in [0-2]) yarn verify ;; 3) npm run clean && yarn compile && yarn add typescript@2.0.10 && yarn test ;; 4) npm run clean && yarn compile && yarn add typescript@next && yarn test ;; esac: parallel: true deployment: npm-latest: diff --git a/scripts/assertMinCircleNodes.js b/scripts/assertMinCircleNodes.js index 449864e72e7..ef418463d1a 100644 --- a/scripts/assertMinCircleNodes.js +++ b/scripts/assertMinCircleNodes.js @@ -1,7 +1,7 @@ -var requiredNodes = 4; +var requiredNodes = 5; var nodes = parseInt(process.argv[2], 10); if (requiredNodes != null && requiredNodes > nodes) { - console.error("ERROR: You must run CircleCI with 4 parallel nodes"); + console.error("ERROR: You must run CircleCI with " + requiredNodes + " parallel nodes"); console.error(" This ensures that different environments are tested for TSLint compatibility"); console.error(" https://circleci.com/gh//tslint/edit#parallel-builds"); process.exit(1); From 7a782a3ec8dd72b97ddbe83ce36e9f0a0b520c02 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 2 Mar 2017 16:00:02 -0500 Subject: [PATCH 5/8] Allow more granular composition of rule configuration (#2284) --- src/configs/latest.ts | 47 +- src/configs/recommended.ts | 300 ++++++++----- src/configuration.ts | 175 ++++++-- src/enableDisableRules.ts | 32 +- src/language/rule/abstractRule.ts | 32 +- src/language/rule/rule.ts | 5 +- src/language/utils.ts | 2 +- src/linter.ts | 7 +- src/ruleLoader.ts | 41 +- test/config/tslint-custom-rules.json | 2 + .../tslint-extends-package-partial.json | 13 + test/config/tslint-with-comments.json | 8 +- test/configurationTests.ts | 414 ++++++++++-------- test/eofLineRuleTests.ts | 2 +- test/ruleLoaderTests.ts | 118 ++--- test/utils.ts | 11 +- 16 files changed, 713 insertions(+), 496 deletions(-) create mode 100644 test/config/tslint-extends-package-partial.json diff --git a/src/configs/latest.ts b/src/configs/latest.ts index 928abec07e0..7cc43241c04 100644 --- a/src/configs/latest.ts +++ b/src/configs/latest.ts @@ -23,11 +23,13 @@ export const rules = { "no-angle-bracket-type-assertion": true, // added in v4.1 - "only-arrow-functions": [true, - "allow-declarations", - // the following option was added in 4.1 - "allow-named-functions", - ], + "only-arrow-functions": { + options: [ + "allow-declarations", + // the following option was added in 4.1 + "allow-named-functions", + ], + }, "prefer-const": true, // added in v4.2 @@ -38,13 +40,15 @@ export const rules = { // added in v4.3 "import-spacing": true, - "space-before-function-paren": [true, { - "anonymous": "never", - "asyncArrow": "always", - "constructor": "never", - "method": "never", - "named": "never", - }], + "space-before-function-paren": { + options: { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never", + }, + }, "typeof-compare": true, "unified-signatures": true, @@ -54,15 +58,16 @@ export const rules = { "no-misused-new": true, // added in v4.5 - "ban-types": [ - true, - ["Object", "Avoid using the `Object` type. Did you mean `object`?"], - ["Function", "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."], - ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], - ["Number", "Avoid using the `Number` type. Did you mean `number`?"], - ["String", "Avoid using the `String` type. Did you mean `string`?"], - ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"], - ], + "ban-types": { + options: [ + ["Object", "Avoid using the `Object` type. Did you mean `object`?"], + ["Function", "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."], + ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], + ["Number", "Avoid using the `Number` type. Did you mean `number`?"], + ["String", "Avoid using the `String` type. Did you mean `string`?"], + ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"], + ], + }, "no-duplicate-super": true, }; // tslint:enable object-literal-sort-keys diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 23e3a64f81b..9247df857dc 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -15,47 +15,62 @@ * limitations under the License. */ -/* tslint:disable:object-literal-key-quotes */ export const rules = { "adjacent-overload-signatures": true, - "align": [true, - "parameters", - "statements", - ], - "array-type": [true, "array-simple"], + "align": { + options: [ + "parameters", + "statements", + ], + }, + "array-type": { + options: ["array-simple"], + }, "arrow-parens": true, "class-name": true, - "comment-format": [true, - "check-space", - ], + "comment-format": { + options: ["check-space"], + }, "curly": true, "cyclomatic-complexity": false, "eofline": true, "forin": true, - "indent": [true, "spaces"], - "interface-name": [true, "always-prefix"], + "indent": { + options: ["spaces"], + }, + "interface-name": { + options: ["always-prefix"], + }, "jsdoc-format": true, "label-position": true, - "max-classes-per-file": [true, 1], - "max-line-length": [true, 120], + "max-classes-per-file": { + options: [1], + }, + "max-line-length": { + options: [120], + }, "member-access": true, - "member-ordering": [true, { - "order": "statics-first", - }], + "member-ordering": { + options: { + order: "statics-first", + }, + }, "new-parens": true, "no-any": false, "no-arg": true, "no-bitwise": true, "no-conditional-assignment": true, "no-consecutive-blank-lines": true, - "no-console": [true, - "debug", - "info", - "log", - "time", - "timeEnd", - "trace", - ], + "no-console": { + options: [ + "debug", + "info", + "log", + "time", + "timeEnd", + "trace", + ], + }, "no-construct": true, "no-debugger": true, "no-empty": true, @@ -75,89 +90,122 @@ export const rules = { "no-use-before-declare": false, "no-var-keyword": true, "no-var-requires": true, - "object-literal-key-quotes": [true, "consistent-as-needed"], + "object-literal-key-quotes": { + options: ["consistent-as-needed"], + }, "object-literal-shorthand": true, "object-literal-sort-keys": true, - "one-line": [true, - "check-catch", - "check-else", - "check-finally", - "check-open-brace", - "check-whitespace", - ], - "one-variable-per-declaration": [true, - "ignore-for-loop", - ], - "only-arrow-functions": [true, "allow-declarations"], - "ordered-imports": [true, { - "import-sources-order": "case-insensitive", - "named-imports-order": "case-insensitive", - }], + "one-line": { + options: [ + "check-catch", + "check-else", + "check-finally", + "check-open-brace", + "check-whitespace", + ], + }, + "one-variable-per-declaration": { + options: ["ignore-for-loop"], + }, + "only-arrow-functions": { + options: ["allow-declarations"], + }, + "ordered-imports": { + options: { + "import-sources-order": "case-insensitive", + "named-imports-order": "case-insensitive", + }, + }, "prefer-for-of": true, - "quotemark": [true, "double", "avoid-escape"], + "quotemark": { + options: [ + "double", + "avoid-escape", + ], + }, "radix": true, - "semicolon": [true, "always"], + "semicolon": { + options: ["always"], + }, "switch-default": true, - "trailing-comma": [true, { - "multiline": "always", - "singleline": "never", - }], - "triple-equals": [true, "allow-null-check"], + "trailing-comma": { + options: { + multiline: "always", + singleline: "never", + }, + }, + "triple-equals": { + options: ["allow-null-check"], + }, "typedef": false, - "typedef-whitespace": [true, { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace", - }, { - "call-signature": "onespace", - "index-signature": "onespace", - "parameter": "onespace", - "property-declaration": "onespace", - "variable-declaration": "onespace", - }], + "typedef-whitespace": { + options: [{ + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace", + }, { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace", + }], + }, "use-isnan": true, - "variable-name": [true, - "ban-keywords", - "check-format", - "allow-pascal-case", - ], - "whitespace": [true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type", - "check-typecast", - ], + "variable-name": { + options: [ + "ban-keywords", + "check-format", + "allow-pascal-case", + ], + }, + "whitespace": { + options: [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast", + ], + }, }; export const jsRules = { - "align": [true, - "parameters", - "statements", - ], + "align": { + options: [ + "parameters", + "statements", + ], + }, "class-name": true, "curly": true, "eofline": true, "forin": true, - "indent": [true, "spaces"], + "indent": { + options: ["spaces"], + }, "jsdoc-format": true, "label-position": true, - "max-line-length": [true, 120], + "max-line-length": { + options: [120], + }, "new-parens": true, "no-arg": true, "no-bitwise": true, "no-conditional-assignment": true, "no-consecutive-blank-lines": true, - "no-console": [true, - "debug", - "info", - "log", - "time", - "timeEnd", - "trace", - ], + "no-console": { + options: [ + "debug", + "info", + "log", + "time", + "timeEnd", + "trace", + ], + }, "no-construct": true, "no-debugger": true, "no-duplicate-variable": true, @@ -173,38 +221,54 @@ export const jsRules = { // disable this rule as it is very heavy performance-wise and not that useful "no-use-before-declare": false, "object-literal-sort-keys": true, - "one-line": [true, - "check-catch", - "check-else", - "check-finally", - "check-open-brace", - "check-whitespace", - ], - "one-variable-per-declaration": [true, - "ignore-for-loop", - ], - "quotemark": [true, "double", "avoid-escape"], + "one-line": { + options: [ + "check-catch", + "check-else", + "check-finally", + "check-open-brace", + "check-whitespace", + ], + }, + "one-variable-per-declaration": { + options: ["ignore-for-loop"], + }, + "quotemark": { + options: [ + "double", + "avoid-escape", + ], + }, "radix": true, - "semicolon": [true, "always"], + "semicolon": { + options: ["always"], + }, "switch-default": true, - "trailing-comma": [true, { - "multiline": "always", - "singleline": "never", - }], - "triple-equals": [true, "allow-null-check"], + "trailing-comma": { + options: { + multiline: "always", + singleline: "never", + }, + }, + "triple-equals": { + options: ["allow-null-check"], + }, "use-isnan": true, - "variable-name": [true, - "ban-keywords", - "check-format", - "allow-pascal-case", - ], - "whitespace": [true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type", - "check-typecast", - ], + "variable-name": { + options: [ + "ban-keywords", + "check-format", + "allow-pascal-case", + ], + }, + "whitespace": { + options: [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast", + ], + }, }; -/* tslint:enable:object-literal-key-quotes */ diff --git a/src/configuration.ts b/src/configuration.ts index 2039dea15f5..7b3e9bc0ac8 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -21,16 +21,17 @@ import * as path from "path"; import * as resolve from "resolve"; import { FatalError } from "./error"; +import { IOptions, RuleSeverity } from "./language/rule/rule"; import { arrayify, objectify, stripComments } from "./utils"; export interface IConfigurationFile { - extends?: string | string[]; - jsRules?: any; + extends: string[]; + jsRules: Map>; linterOptions?: { typeCheck?: boolean, }; - rulesDirectory?: string | string[]; - rules?: any; + rulesDirectory: string[]; + rules: Map>; } export interface IConfigurationLoadResult { @@ -39,8 +40,19 @@ export interface IConfigurationLoadResult { } export const CONFIG_FILENAME = "tslint.json"; -export const DEFAULT_CONFIG = { - extends: "tslint:recommended", + +export const DEFAULT_CONFIG: IConfigurationFile = { + extends: ["tslint:recommended"], + jsRules: new Map>(), + rules: new Map>(), + rulesDirectory: [], +}; + +export const EMPTY_CONFIG: IConfigurationFile = { + extends: [], + jsRules: new Map>(), + rules: new Map>(), + rulesDirectory: [], }; const BUILT_IN_CONFIG = /^tslint:(.*)$/; @@ -115,28 +127,28 @@ export function loadConfigurationFromPath(configFilePath?: string): IConfigurati return DEFAULT_CONFIG; } else { const resolvedConfigFilePath = resolveConfigurationPath(configFilePath); - let configFile: IConfigurationFile; + let rawConfigFile: any; if (path.extname(resolvedConfigFilePath) === ".json") { const fileContent = stripComments(fs.readFileSync(resolvedConfigFilePath) .toString() .replace(/^\uFEFF/, "")); - configFile = JSON.parse(fileContent); + rawConfigFile = JSON.parse(fileContent); } else { - configFile = require(resolvedConfigFilePath); + rawConfigFile = require(resolvedConfigFilePath); delete require.cache[resolvedConfigFilePath]; } const configFileDir = path.dirname(resolvedConfigFilePath); + const configFile = parseConfigFile(rawConfigFile, configFileDir); - configFile.rulesDirectory = getRulesDirectories(configFile.rulesDirectory, configFileDir); // load configurations, in order, using their identifiers or relative paths // apply the current configuration last by placing it last in this array - const configs = arrayify(configFile.extends).map((name) => { + const configs = configFile.extends.map((name) => { const nextConfigFilePath = resolveConfigurationPath(name, configFileDir); return loadConfigurationFromPath(nextConfigFilePath); }).concat([configFile]); - return configs.reduce(extendConfigurationFile, {}); + return configs.reduce(extendConfigurationFile, EMPTY_CONFIG); } } @@ -172,11 +184,6 @@ function resolveConfigurationPath(filePath: string, relativeTo?: string) { export function extendConfigurationFile(targetConfig: IConfigurationFile, nextConfigSource: IConfigurationFile): IConfigurationFile { - const combinedConfig: IConfigurationFile = {}; - - const configRulesDirectory = arrayify(targetConfig.rulesDirectory); - const nextConfigRulesDirectory = arrayify(nextConfigSource.rulesDirectory); - combinedConfig.rulesDirectory = configRulesDirectory.concat(nextConfigRulesDirectory); const combineProperties = (targetProperty: any, nextProperty: any) => { const combinedProperty: any = {}; @@ -190,11 +197,32 @@ export function extendConfigurationFile(targetConfig: IConfigurationFile, return combinedProperty; }; - combinedConfig.rules = combineProperties(targetConfig.rules, nextConfigSource.rules); - combinedConfig.jsRules = combineProperties(targetConfig.jsRules, nextConfigSource.jsRules); - combinedConfig.linterOptions = combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions); + const combineMaps = (target: Map>, next: Map>) => { + const combined = new Map>(); + target.forEach((options, ruleName) => { + combined.set(ruleName, options); + }); + next.forEach((options, ruleName) => { + const combinedRule = combined.get(ruleName); + if (combinedRule != null) { + combined.set(ruleName, combineProperties(combinedRule, options)); + } else { + combined.set(ruleName, options); + } + }); + return combined; + }; - return combinedConfig; + const combinedRulesDirs = targetConfig.rulesDirectory.concat(nextConfigSource.rulesDirectory); + const dedupedRulesDirs = Array.from(new Set(combinedRulesDirs)); + + return { + extends: [], + jsRules: combineMaps(targetConfig.jsRules, nextConfigSource.jsRules), + linterOptions: combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions), + rules: combineMaps(targetConfig.rules, nextConfigSource.rules), + rulesDirectory: dedupedRulesDirs, + }; } function getHomeDir() { @@ -243,29 +271,102 @@ export function getRulesDirectories(directories?: string | string[], relativeTo? return rulesDirectories; } -export function isRuleEnabled(ruleConfigValue: any): boolean { - if (typeof ruleConfigValue === "boolean") { - return ruleConfigValue; - } - - if (Array.isArray(ruleConfigValue) && ruleConfigValue.length > 0) { - return ruleConfigValue[0]; +/** + * Parses the options of a single rule and upgrades legacy settings such as `true`, `[true, "option"]` + * + * @param ruleConfigValue The raw option setting of a rule + */ +function parseRuleOptions(ruleConfigValue: any): Partial { + let ruleArguments: any[] | undefined; + let ruleSeverity: RuleSeverity | undefined; + + if (ruleConfigValue == null) { + ruleArguments = []; + ruleSeverity = "off"; + } else if (Array.isArray(ruleConfigValue) && ruleConfigValue.length > 0) { + // old style: array + ruleArguments = ruleConfigValue.slice(1); + ruleSeverity = ruleConfigValue[0] === true ? "error" : "off"; + } else if (typeof ruleConfigValue === "boolean") { + // old style: boolean + ruleArguments = []; + ruleSeverity = ruleConfigValue === true ? "error" : "off"; + } else if (ruleConfigValue.severity) { + switch (ruleConfigValue.severity.toLowerCase()) { + case "warn": + case "warning": + ruleSeverity = "warning"; + break; + case "error": + ruleSeverity = "error"; + break; + default: + ruleSeverity = "off"; + } + } else if (typeof ruleConfigValue === "object") { + ruleSeverity = undefined; + } else { + ruleSeverity = "off"; } - if (ruleConfigValue.severity !== "off" && ruleConfigValue.severity !== "none") { - return true; + if (ruleConfigValue && ruleConfigValue.options) { + ruleArguments = arrayify(ruleConfigValue.options); } - return false; + return { + ruleArguments, + ruleSeverity, + }; } -export function getRuleSeverity(ruleConfigValue: any) { - if (ruleConfigValue.severity && - (ruleConfigValue.severity.toLowerCase() === "warn" || - ruleConfigValue.severity.toLowerCase() === "warning")) { +/** + * Parses a config file and normalizes legacy config settings + * + * @param configFile The raw object read from the JSON of a config file + * @param configFileDir The directory of the config file + */ +export function parseConfigFile(configFile: any, configFileDir?: string): IConfigurationFile { + const rules = new Map>(); + const jsRules = new Map>(); + + if (configFile.rules) { + for (const ruleName in configFile.rules) { + if (configFile.rules.hasOwnProperty(ruleName)) { + rules.set(ruleName, parseRuleOptions(configFile.rules[ruleName])); + } + } + } - return "warning"; + if (configFile.jsRules) { + for (const ruleName in configFile.jsRules) { + if (configFile.jsRules.hasOwnProperty(ruleName)) { + jsRules.set(ruleName, parseRuleOptions(configFile.jsRules[ruleName])); + } + } } - return "error"; + return { + extends: arrayify(configFile.extends), + jsRules, + linterOptions: configFile.linterOptions || {}, + rulesDirectory: getRulesDirectories(configFile.rulesDirectory, configFileDir), + rules, + }; +} + +/** + * Fills in default values for `IOption` properties and outputs an array of `IOption` + */ +export function convertRuleOptions(ruleConfiguration: Map>): IOptions[] { + const output: IOptions[] = []; + ruleConfiguration.forEach((partialOptions, ruleName) => { + const options: IOptions = { + disabledIntervals: [], + ruleArguments: partialOptions.ruleArguments || [], + ruleName, + ruleSeverity: partialOptions.ruleSeverity || "error", + }; + output.push(options); + }); + return output; } diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index ac585208ec5..64d4cdccfde 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -17,26 +17,24 @@ import * as utils from "tsutils"; import * as ts from "typescript"; +import { IOptions } from "./language/rule/rule"; -import {isRuleEnabled} from "./configuration"; -import {IEnableDisablePosition} from "./ruleLoader"; +import { IEnableDisablePosition } from "./ruleLoader"; export class EnableDisableRulesWalker { - private enableDisableRuleMap: {[rulename: string]: IEnableDisablePosition[]}; + private enableDisableRuleMap: Map; private enabledRules: string[]; - constructor(private sourceFile: ts.SourceFile, rules: {[name: string]: any}) { - this.enableDisableRuleMap = {}; + constructor(private sourceFile: ts.SourceFile, ruleOptionsList: IOptions[]) { + this.enableDisableRuleMap = new Map(); this.enabledRules = []; - if (rules) { - for (const rule of Object.keys(rules)) { - if (isRuleEnabled(rules[rule])) { - this.enabledRules.push(rule); - this.enableDisableRuleMap[rule] = [{ - isEnabled: true, - position: 0, - }]; - } + for (const ruleOptions of ruleOptionsList) { + if (ruleOptions.ruleSeverity !== "off") { + this.enabledRules.push(ruleOptions.ruleName); + this.enableDisableRuleMap.set(ruleOptions.ruleName, [{ + isEnabled: true, + position: 0, + }]); } } } @@ -64,7 +62,7 @@ export class EnableDisableRulesWalker { } private switchRuleState(ruleName: string, isEnabled: boolean, start: number, end?: number): void { - const ruleStateMap = this.enableDisableRuleMap[ruleName]; + const ruleStateMap = this.enableDisableRuleMap.get(ruleName); if (ruleStateMap === undefined || // skip switches for unknown or disabled rules isEnabled === ruleStateMap[ruleStateMap.length - 1].isEnabled // no need to add switch points if there is no change ) { @@ -97,8 +95,8 @@ export class EnableDisableRulesWalker { // split at whitespaces // filter empty items coming from whitespaces at start, at end or empty list let rulesList = commentText.substr(match[0].length) - .split(/\s+/) - .filter((rule) => !!rule); + .split(/\s+/) + .filter((rule) => !!rule); if (rulesList.length === 0 && match[3] === ":") { // nothing to do here: an explicit separator was specified but no rules to switch return; diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 33bff73978e..6942b4e00e0 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -17,38 +17,24 @@ import * as ts from "typescript"; -import { getRuleSeverity, isRuleEnabled } from "../../configuration"; -import {arrayify} from "../../utils"; import {doesIntersect} from "../utils"; import {IWalker, WalkContext} from "../walker"; -import { IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure, RuleSeverity } from "./rule"; +import { IOptions, IRule, IRuleMetadata, RuleFailure, RuleSeverity } from "./rule"; export abstract class AbstractRule implements IRule { public static metadata: IRuleMetadata; protected readonly ruleArguments: any[]; protected readonly ruleSeverity: RuleSeverity; + public ruleName: string; - constructor(public readonly ruleName: string, private value: any, private disabledIntervals: IDisabledInterval[]) { - if (Array.isArray(value) && value.length > 1) { - this.ruleArguments = value.slice(1); - } else { - this.ruleArguments = []; - } - - if (value.options) { - this.ruleArguments = arrayify(value.options); - } - - this.ruleSeverity = getRuleSeverity(value); + constructor(private options: IOptions) { + this.ruleName = options.ruleName; + this.ruleArguments = options.ruleArguments; + this.ruleSeverity = options.ruleSeverity; } public getOptions(): IOptions { - return { - disabledIntervals: this.disabledIntervals, - ruleArguments: this.ruleArguments, - ruleName: this.ruleName, - ruleSeverity: this.ruleSeverity, - }; + return this.options; } public abstract apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; @@ -59,7 +45,7 @@ export abstract class AbstractRule implements IRule { } public isEnabled(): boolean { - return isRuleEnabled(this.value); + return this.ruleSeverity !== "off"; } protected applyWithFunction(sourceFile: ts.SourceFile, walkFn: (ctx: WalkContext) => void): RuleFailure[]; @@ -74,7 +60,7 @@ export abstract class AbstractRule implements IRule { const result: RuleFailure[] = []; for (const failure of failures) { // don't add failures for a rule if the failure intersects an interval where that rule is disabled - if (!doesIntersect(failure, this.disabledIntervals) && !result.some((f) => f.equals(failure))) { + if (!doesIntersect(failure, this.options.disabledIntervals) && !result.some((f) => f.equals(failure))) { result.push(failure); } } diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index f9b2637baf3..4af0147221c 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -86,12 +86,11 @@ export interface IRuleMetadata { export type RuleType = "functionality" | "maintainability" | "style" | "typescript"; -export type RuleSeverity = "warning" | "error"; +export type RuleSeverity = "warning" | "error" | "off"; export interface IOptions { ruleArguments: any[]; ruleSeverity: RuleSeverity; - ruleName: string; disabledIntervals: IDisabledInterval[]; } @@ -245,7 +244,7 @@ export class RuleFailure { this.startPosition = this.createFailurePosition(start); this.endPosition = this.createFailurePosition(end); this.rawLines = sourceFile.text; - this.ruleSeverity = "warning"; + this.ruleSeverity = "error"; } public getFileName() { diff --git a/src/language/utils.ts b/src/language/utils.ts index 8baf0d1ce24..039ddea4d78 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -107,7 +107,7 @@ export function isBlockScopedBindingElement(node: ts.BindingElement): boolean { } export function getBindingElementVariableDeclaration(node: ts.BindingElement): ts.VariableDeclaration | null { - let currentParent = node.parent!; + let currentParent = node.parent! as ts.Node; while (currentParent.kind !== ts.SyntaxKind.VariableDeclaration) { if (currentParent.parent == null) { return null; // function parameter, no variable declaration diff --git a/src/linter.ts b/src/linter.ts index dc725a833ce..42e8e7013a4 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -20,6 +20,7 @@ import * as path from "path"; import * as ts from "typescript"; import { + convertRuleOptions, DEFAULT_CONFIG, findConfiguration, findConfigurationPath, @@ -201,14 +202,14 @@ class Linter { } private getEnabledRules(sourceFile: ts.SourceFile, configuration: IConfigurationFile = DEFAULT_CONFIG, isJs: boolean): IRule[] { - const configurationRules = isJs ? configuration.jsRules : configuration.rules; + const ruleOptionsList = convertRuleOptions(isJs ? configuration.jsRules : configuration.rules); // walk the code first to find all the intervals where rules are disabled - const enableDisableRuleMap = new EnableDisableRulesWalker(sourceFile, configurationRules).getEnableDisableRuleMap(); + const enableDisableRuleMap = new EnableDisableRulesWalker(sourceFile, ruleOptionsList).getEnableDisableRuleMap(); const rulesDirectories = arrayify(this.options.rulesDirectory) .concat(arrayify(configuration.rulesDirectory)); - const configuredRules = loadRules(configurationRules, enableDisableRuleMap, rulesDirectories, isJs); + const configuredRules = loadRules(ruleOptionsList, enableDisableRuleMap, rulesDirectories, isJs); return configuredRules.filter((r) => r.isEnabled()); } diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index 4ace0397ab8..1fbc9ec0942 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -18,10 +18,10 @@ import * as fs from "fs"; import * as path from "path"; -import { getRelativePath, isRuleEnabled } from "./configuration"; +import { getRelativePath } from "./configuration"; import { showWarningOnce } from "./error"; import { AbstractRule } from "./language/rule/abstractRule"; -import { IDisabledInterval, IRule } from "./language/rule/rule"; +import { IDisabledInterval, IOptions, IRule } from "./language/rule/rule"; import { arrayify, camelize, dedent } from "./utils"; const moduleDirectory = path.dirname(module.filename); @@ -33,32 +33,31 @@ export interface IEnableDisablePosition { position: number; } -export function loadRules(ruleConfiguration: {[name: string]: any}, - enableDisableRuleMap: {[rulename: string]: IEnableDisablePosition[]}, +export function loadRules(ruleOptionsList: IOptions[], + enableDisableRuleMap: Map, rulesDirectories?: string | string[], isJs?: boolean): IRule[] { const rules: IRule[] = []; const notFoundRules: string[] = []; const notAllowedInJsRules: string[] = []; - for (const ruleName in ruleConfiguration) { - if (ruleConfiguration.hasOwnProperty(ruleName)) { - const ruleValue = ruleConfiguration[ruleName]; - if (isRuleEnabled(ruleValue) || enableDisableRuleMap.hasOwnProperty(ruleName)) { - const Rule: (typeof AbstractRule) | null = findRule(ruleName, rulesDirectories); - if (Rule == null) { - notFoundRules.push(ruleName); + for (const ruleOptions of ruleOptionsList) { + const ruleName = ruleOptions.ruleName; + const enableDisableRules = enableDisableRuleMap.get(ruleName); + if (ruleOptions.ruleSeverity !== "off" || enableDisableRuleMap) { + const Rule: (typeof AbstractRule) | null = findRule(ruleName, rulesDirectories); + if (Rule == null) { + notFoundRules.push(ruleName); + } else { + if (isJs && Rule.metadata && Rule.metadata.typescriptOnly) { + notAllowedInJsRules.push(ruleName); } else { - if (isJs && Rule.metadata && Rule.metadata.typescriptOnly != null && Rule.metadata.typescriptOnly) { - notAllowedInJsRules.push(ruleName); - } else { - const ruleSpecificList = (ruleName in enableDisableRuleMap ? enableDisableRuleMap[ruleName] : []); - const disabledIntervals = buildDisabledIntervalsFromSwitches(ruleSpecificList); - rules.push(new (Rule as any)(ruleName, ruleValue, disabledIntervals)); - - if (Rule.metadata && Rule.metadata.deprecationMessage) { - showWarningOnce(`${Rule.metadata.ruleName} is deprecated. ${Rule.metadata.deprecationMessage}`); - } + const ruleSpecificList = enableDisableRules || []; + ruleOptions.disabledIntervals = buildDisabledIntervalsFromSwitches(ruleSpecificList); + rules.push(new (Rule as any)(ruleOptions)); + + if (Rule.metadata && Rule.metadata.deprecationMessage) { + showWarningOnce(`${Rule.metadata.ruleName} is deprecated. ${Rule.metadata.deprecationMessage}`); } } } diff --git a/test/config/tslint-custom-rules.json b/test/config/tslint-custom-rules.json index ce5d5273980..de800c178ad 100644 --- a/test/config/tslint-custom-rules.json +++ b/test/config/tslint-custom-rules.json @@ -1,11 +1,13 @@ { "jsRules": { "always-fail": { + "options": [1], "severity": "error" } }, "rules": { "always-fail": { + "options": 1, "severity": "error" } } diff --git a/test/config/tslint-extends-package-partial.json b/test/config/tslint-extends-package-partial.json new file mode 100644 index 00000000000..eebddcc6419 --- /dev/null +++ b/test/config/tslint-extends-package-partial.json @@ -0,0 +1,13 @@ +{ + "extends": "tslint-test-custom-rules", + "jsRules": { + "always-fail": { + "severity": "warning" + } + }, + "rules": { + "always-fail": { + "options": [2] + } + } +} diff --git a/test/config/tslint-with-comments.json b/test/config/tslint-with-comments.json index 157a5f63515..190049a66b7 100644 --- a/test/config/tslint-with-comments.json +++ b/test/config/tslint-with-comments.json @@ -7,8 +7,8 @@ "rule-two": { "severity": "error" // after comment }, - "rule-three": "//not a comment", - "rule-four": "/*also not a comment*/" + "rule-three": [true, "//not a comment"], + "rule-four": [true, "/*also not a comment*/"] }, // a nice comment "rules": { @@ -19,7 +19,7 @@ "severity": "error" // after comment }, - "rule-three": "//not a comment", - "rule-four": "/*also not a comment*/" + "rule-three": [true, "//not a comment"], + "rule-four": [true, "/*also not a comment*/"] } } diff --git a/test/configurationTests.ts b/test/configurationTests.ts index e2c716e16cf..6217d7dd871 100644 --- a/test/configurationTests.ts +++ b/test/configurationTests.ts @@ -16,141 +16,218 @@ import * as fs from "fs"; -import { extendConfigurationFile, IConfigurationFile, loadConfigurationFromPath } from "../src/configuration"; +import { + convertRuleOptions, + extendConfigurationFile, + IConfigurationFile, + loadConfigurationFromPath, + parseConfigFile, +} from "../src/configuration"; +import { IOptions } from "./../src/language/rule/rule"; import { createTempFile } from "./utils"; -describe("Configuration", () => { - it("extendConfigurationFile", () => { - const EMPTY_CONFIG: IConfigurationFile = { - jsRules: {}, - linterOptions: {}, - rules: {}, - rulesDirectory: [], - }; - - assert.deepEqual(extendConfigurationFile({}, {}), EMPTY_CONFIG); - assert.deepEqual(extendConfigurationFile({}, EMPTY_CONFIG), EMPTY_CONFIG); - assert.deepEqual(extendConfigurationFile(EMPTY_CONFIG, {}), EMPTY_CONFIG); - assert.deepEqual(extendConfigurationFile({}, { - jsRules: { row: "oar" }, - linterOptions: {}, - rules: { foo: "bar" }, - rulesDirectory: "foo", - }), { - jsRules: { row: "oar" }, - linterOptions: {}, - rules: {foo: "bar"}, - rulesDirectory: ["foo"], +describe.only("Configuration", () => { + describe("parseConfigFile", () => { + it("parses empty config", () => { + const rawConfig = { + }; + const expected = getEmptyConfig(); + assertConfigEquals(parseConfigFile(rawConfig), expected); }); - const actualConfig = extendConfigurationFile({ - jsRules: { row: "oar" }, - linterOptions: {}, - rules: { - a: 1, - b: 1, - }, - rulesDirectory: ["foo", "bar"], - }, { - jsRules: { fly: "wings" }, - linterOptions: {}, - rules: { - b: 2, - c: 3, - }, - rulesDirectory: "baz", + + it("arrayifies `extends`", () => { + const rawConfig = { + extends: "a", + }; + const expected = getEmptyConfig(); + expected.extends = ["a"]; + assertConfigEquals(parseConfigFile(rawConfig), expected); + }); + + it("parses different ways of storing options", () => { + const rawConfig = { + rules: { + a: true, + b: [true], + c: false, + d: [false], + e: [true, 1], + f: [false, 2, 3], + g: { severity: "off"}, + h: { severity: "warn"}, + i: { severity: "warning"}, + j: { severity: "error"}, + k: { severity: "none" }, + l: { options: 1 }, + m: { options: [2] }, + n: { options: [{ no: false }] }, + o: { severity: "warn", options: 1 }, + p: null, + q: {}, + r: "garbage", + s: { junk: 1 }, + }, + }; + const expected = getEmptyConfig(); + expected.rules.set("a", { ruleArguments: [], ruleSeverity: "error" }); + expected.rules.set("b", { ruleArguments: [], ruleSeverity: "error" }); + expected.rules.set("c", { ruleArguments: [], ruleSeverity: "off" }); + expected.rules.set("d", { ruleArguments: [], ruleSeverity: "off" }); + expected.rules.set("e", { ruleArguments: [1], ruleSeverity: "error" }); + expected.rules.set("f", { ruleArguments: [2, 3], ruleSeverity: "off" }); + expected.rules.set("g", { ruleArguments: undefined, ruleSeverity: "off" }); + expected.rules.set("h", { ruleArguments: undefined, ruleSeverity: "warning" }); + expected.rules.set("i", { ruleArguments: undefined, ruleSeverity: "warning" }); + expected.rules.set("j", { ruleArguments: undefined, ruleSeverity: "error" }); + expected.rules.set("k", { ruleArguments: undefined, ruleSeverity: "off" }); + expected.rules.set("l", { ruleArguments: [1], ruleSeverity: undefined }); + expected.rules.set("m", { ruleArguments: [2], ruleSeverity: undefined }); + expected.rules.set("n", { ruleArguments: [{ no: false }], ruleSeverity: undefined }); + expected.rules.set("o", { ruleArguments: [1], ruleSeverity: "warning" }); + expected.rules.set("p", { ruleArguments: [], ruleSeverity: "off" }); + expected.rules.set("q", { ruleArguments: undefined, ruleSeverity: undefined }); + expected.rules.set("r", { ruleArguments: undefined, ruleSeverity: "off" }); + expected.rules.set("s", { ruleArguments: undefined, ruleSeverity: undefined }); + assertConfigEquals(parseConfigFile(rawConfig), expected); + }); + + it("fills in default values", () => { + const initial = getEmptyConfig(); + initial.rules.set("s", { ruleArguments: undefined, ruleSeverity: undefined }); + assert.deepEqual(convertRuleOptions(initial.rules)[0], { + disabledIntervals: [], + ruleArguments: [], + ruleName: "s", + ruleSeverity: "error", }); - /* tslint:disable:object-literal-sort-keys */ - const expectedConfig = { - jsRules: { - row: "oar", - fly: "wings", - }, - linterOptions: {}, - rules: { - a: 1, - b: 2, - c: 3, - }, - rulesDirectory: ["foo", "bar", "baz"], - }; - assert.deepEqual(actualConfig, expectedConfig); + }); + }); + + describe("extendConfigurationFile", () => { + const EMPTY_CONFIG = getEmptyConfig(); + + it("verifies that empty extending empty is empty", () => { + assertConfigEquals(extendConfigurationFile(EMPTY_CONFIG, EMPTY_CONFIG), EMPTY_CONFIG); + }); + + it("extends empty with non-empty", () => { + const config = getEmptyConfig(); + config.jsRules.set("row", { ruleArguments: ["oar", "column"], ruleSeverity: "error" }); + config.rules.set("foo", { ruleArguments: ["bar"], ruleSeverity: "off" }); + config.rulesDirectory = ["foo"]; + config.linterOptions = { typeCheck: true }; + assertConfigEquals(extendConfigurationFile(EMPTY_CONFIG, config), config); + }); + + it("extends empty, with undefined ruleSeverity or ruleArguments", () => { + const config = getEmptyConfig(); + config.jsRules.set("row", { ruleArguments: ["oar", "column"] }); + config.rules.set("foo", { ruleSeverity: "off" }); + config.linterOptions = { }; + assertConfigEquals(extendConfigurationFile(EMPTY_CONFIG, config), config); + }); + + it ("unions values", () => { + const baseConfig = getEmptyConfig(); + baseConfig.rules.set("foo", { ruleArguments: ["bar"], ruleSeverity: "off" }); + baseConfig.jsRules.set("row", { ruleArguments: ["oar", "column"] }); + baseConfig.rulesDirectory = ["foo"]; + + const extendingConfig = getEmptyConfig(); + extendingConfig.rules.set("flow", { ruleArguments: ["river"] }); + extendingConfig.jsRules.set("good", { ruleArguments: ["does"], ruleSeverity: "warning" }); + extendingConfig.rulesDirectory = ["baz"]; + + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("foo", { ruleArguments: ["bar"], ruleSeverity: "off" }); + expectedConfig.rules.set("flow", { ruleArguments: ["river"] }); + expectedConfig.jsRules.set("row", { ruleArguments: ["oar", "column"] }); + expectedConfig.jsRules.set("good", { ruleArguments: ["does"], ruleSeverity: "warning" }); + expectedConfig.rulesDirectory = ["foo", "baz"]; + + const actualConfig = extendConfigurationFile(baseConfig, extendingConfig); + assertConfigEquals(actualConfig, expectedConfig); + }); + + it ("overrides values", () => { + const baseConfig = getEmptyConfig(); + baseConfig.rules.set("foo", { ruleArguments: ["bar"], ruleSeverity: "off" }); + baseConfig.jsRules.set("row", { ruleArguments: ["oar", "column"] }); + baseConfig.rulesDirectory = ["foo"]; + + const extendingConfig = getEmptyConfig(); + extendingConfig.rules.set("foo", { ruleSeverity: "warning" }); + extendingConfig.jsRules.set("row", { ruleArguments: ["skid"] }); + extendingConfig.rulesDirectory = ["foo"]; + + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("foo", { ruleArguments: ["bar"], ruleSeverity: "warning" }); + expectedConfig.jsRules.set("row", { ruleArguments: ["skid"] }); + expectedConfig.rulesDirectory = ["foo"]; + + const actualConfig = extendConfigurationFile(baseConfig, extendingConfig); + assertConfigEquals(actualConfig, expectedConfig); + }); }); describe("loadConfigurationFromPath", () => { it("extends with relative path", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json"); - assert.isArray(config.rulesDirectory); - assert.equal("error", config.rules["no-fail"].severity, "did not pick up 'no-fail' in base config"); - assert.equal("none", config.rules["always-fail"].severity, "did not set 'always-fail' in top config"); - assert.equal("error", config.jsRules["no-fail"].severity); - assert.equal("none", config.jsRules["always-fail"].severity); + assert.equal("error", config.rules.get("no-fail")!.ruleSeverity, "did not pick up 'no-fail' in base config"); + assert.equal("off", config.rules.get("always-fail")!.ruleSeverity, "did not set 'always-fail' in top config"); + assert.equal("error", config.jsRules.get("no-fail")!.ruleSeverity); + assert.equal("off", config.jsRules.get("always-fail")!.ruleSeverity); }); it("extends with package", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package.json"); + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("rule-one", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-two", { ruleSeverity: "off" }); + expectedConfig.rules.set("rule-three", { ruleSeverity: "error" }); - assert.isArray(config.rulesDirectory); - /* tslint:disable:object-literal-sort-keys */ - assert.deepEqual(config.jsRules, { - "rule-one": true, - "rule-three": { - severity: "none", - }, - "rule-two": { - severity: "error", - }, - }); - assert.deepEqual(config.rules, { - "rule-one": true, - "rule-three": { - severity: "none", - }, - "rule-two": { - severity: "error", - }, - }); - /* tslint:enable:object-literal-sort-keys */ + assertConfigEquals(config.jsRules, expectedConfig.rules); + assertConfigEquals(config.rules, expectedConfig.rules); }); it("extends with package - boolean configuration", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package-boolean.json"); + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("rule-one", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-two", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-three", { ruleSeverity: "off" }); - assert.isArray(config.rulesDirectory); - /* tslint:disable:object-literal-sort-keys */ - assert.deepEqual(config.jsRules, { - "rule-one": true, - "rule-three": false, - "rule-two": true, - }); - assert.deepEqual(config.rules, { - "rule-one": true, - "rule-three": false, - "rule-two": true, - }); - /* tslint:enable:object-literal-sort-keys */ + assertConfigEquals(config.jsRules, expectedConfig.rules); + assertConfigEquals(config.rules, expectedConfig.rules); + }); + + it("extends only severity or only arguments", () => { + const config = loadConfigurationFromPath("./test/config/tslint-extends-package-partial.json"); + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("always-fail", { ruleSeverity: "error", ruleArguments: [2] }); + expectedConfig.jsRules.set("always-fail", { ruleSeverity: "warning", ruleArguments: [1] }); + + assertConfigEquals(config.jsRules, expectedConfig.jsRules); + assertConfigEquals(config.rules, expectedConfig.rules); }); it("extends with package without customization", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package-no-mod.json"); + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("rule-one", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-two", { ruleSeverity: "off" }); - assert.isArray(config.rulesDirectory); - assert.deepEqual(config.jsRules, { - "rule-one": true, - "rule-two": false, - }); - assert.deepEqual(config.rules, { - "rule-one": true, - "rule-two": false, - }); + assertConfigEquals(config.jsRules, expectedConfig.rules); + assertConfigEquals(config.rules, expectedConfig.rules); }); it("extends with builtin", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-builtin.json"); - assert.isUndefined(config.jsRules["no-var-keyword"]); - assert.equal("none", config.jsRules["no-eval"].severity); - assert.isTrue(config.rules["no-var-keyword"]); - assert.equal("none", config.rules["no-eval"].severity); + assert.isUndefined(config.jsRules.get("no-var-keyword")); + assert.equal("off", config.jsRules.get("no-eval")!.ruleSeverity); + assert.equal("error", config.rules.get("no-var-keyword")!.ruleSeverity); + assert.equal("off", config.rules.get("no-eval")!.ruleSeverity); }); describe("with config not relative to tslint", () => { @@ -169,88 +246,53 @@ describe("Configuration", () => { it("extends with package installed relative to tslint", () => { fs.writeFileSync(tmpfile!, JSON.stringify({ extends: "tslint-test-config-non-relative" })); const config = loadConfigurationFromPath(tmpfile!); - assert.deepEqual(config.rules, { - "class-name": true, - }); + + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("class-name", { ruleSeverity: "error" }); + assertConfigEquals(config.rules, expectedConfig.rules); }); }); it("extends with package two levels (and relative path in rulesDirectory)", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package-two-levels.json"); - assert.isArray(config.rulesDirectory); assert.lengthOf(config.rulesDirectory, 2); assert.isTrue(fs.existsSync(config.rulesDirectory![0])); assert.isTrue(fs.existsSync(config.rulesDirectory![1])); - /* tslint:disable:object-literal-sort-keys */ - assert.deepEqual(config.jsRules, { - "always-fail": { - severity: "none", - }, - "rule-one": true, - "rule-two": true, - "rule-four": true, - }); - assert.deepEqual(config.rules, { - "always-fail": { - severity: "none", - }, - "rule-one": true, - "rule-two": true, - "rule-four": true, - }); - /* tslint:enable:object-literal-sort-keys */ + + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("always-fail", { ruleSeverity: "off" }); + expectedConfig.rules.set("rule-one", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-two", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-four", { ruleSeverity: "error" }); + + assertConfigEquals(config.jsRules, expectedConfig.rules); + assertConfigEquals(config.rules, expectedConfig.rules); }); it("extends with array", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-package-array.json"); - assert.isArray(config.rulesDirectory); - assert.deepEqual(config.jsRules, { - "always-fail": { - severity: "none", - }, - "no-fail": { - severity: "error", - }, - "rule-one": true, - "rule-two": { - severity: "error", - }, - }); - assert.deepEqual(config.rules, { - "always-fail": { - severity: "none", - }, - "no-fail": { - severity: "error", - }, - "rule-one": true, - "rule-two": { - severity: "error", - }, - }); + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("always-fail", { ruleSeverity: "off" }); + expectedConfig.rules.set("no-fail", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-one", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-two", { ruleSeverity: "error" }); + + assertConfigEquals(config.jsRules, expectedConfig.rules); + assertConfigEquals(config.rules, expectedConfig.rules); }); it("can load .json files with comments", () => { const config = loadConfigurationFromPath("./test/config/tslint-with-comments.json"); - /* tslint:disable:object-literal-sort-keys */ - assert.deepEqual(config.jsRules, { - "rule-two": { - severity: "error", - }, - "rule-three": "//not a comment", - "rule-four": "/*also not a comment*/", - }); - assert.deepEqual(config.rules, { - "rule-two": { - severity: "error", - }, - "rule-three": "//not a comment", - "rule-four": "/*also not a comment*/", - }); - /* tslint:enable:object-literal-sort-keys */ + const expectedConfig = getEmptyConfig(); + expectedConfig.rules.set("rule-two", { ruleSeverity: "error" }); + expectedConfig.rules.set("rule-three", { ruleSeverity: "error", ruleArguments: ["//not a comment"] }); + expectedConfig.rules.set("rule-four", { ruleSeverity: "error", ruleArguments: ["/*also not a comment*/"] }); + + assertConfigEquals(config.rules, expectedConfig.rules); + assertConfigEquals(config.jsRules, expectedConfig.rules); }); it("can load .json files with BOM", () => { @@ -259,8 +301,8 @@ describe("Configuration", () => { it("can load a built-in configuration", () => { const config = loadConfigurationFromPath("tslint:recommended"); - assert.isTrue(config.jsRules["no-eval"]); - assert.isTrue(config.rules["no-eval"]); + assert.strictEqual("error", config.jsRules.get("no-eval")!.ruleSeverity); + assert.strictEqual("error", config.rules.get("no-eval")!.ruleSeverity); }); it("throws on an invalid built-in configuration path", () => { @@ -270,3 +312,33 @@ describe("Configuration", () => { }); }); }); + +function getEmptyConfig(): IConfigurationFile { + return { + extends: [], + jsRules: new Map>(), + linterOptions: {}, + rules: new Map>(), + rulesDirectory: [], + }; +} + +function demap(map: Map) { + if (map == null) { + return map; + } + const output: { [key: string]: T } = {}; + map.forEach((value, key) => { + output[key] = value; + }); + return output; +} + +// this is needed since `assertConfigEquals` doesn't go into Map object +function assertConfigEquals(actual: any, expected: any) { + assert.deepEqual(actual, expected); + if (actual && (actual.jsRules || actual.rules)) { + assert.deepEqual(demap(actual.jsRules), demap(expected.jsRules)); + assert.deepEqual(demap(actual.rules), demap(expected.rules)); + } +} diff --git a/test/eofLineRuleTests.ts b/test/eofLineRuleTests.ts index 800d4794bb3..69104a284a9 100644 --- a/test/eofLineRuleTests.ts +++ b/test/eofLineRuleTests.ts @@ -23,7 +23,7 @@ describe("", () => { const failureString = Rule.FAILURE_STRING; it("ensures a trailing newline at EOF", () => { - const actualFailures = TestUtils.applyRuleOnFile(fileName, EofLineRule); + const actualFailures = TestUtils.applyRuleOnFile(fileName, EofLineRule!); const expectedFailure = TestUtils.createFailure(fileName, [4, 38], [4, 38], failureString); assert.equal(actualFailures.length, 1); diff --git a/test/ruleLoaderTests.ts b/test/ruleLoaderTests.ts index 4a2778189f0..cbdcfeb77fc 100644 --- a/test/ruleLoaderTests.ts +++ b/test/ruleLoaderTests.ts @@ -18,7 +18,9 @@ import * as diff from "diff"; import * as fs from "fs"; import * as path from "path"; +import { IEnableDisablePosition } from "../src/ruleLoader"; import { camelize } from "../src/utils"; +import { IOptions } from "./../src/language/rule/rule"; import { loadRules } from "./lint"; describe("Rule Loader", () => { @@ -27,104 +29,72 @@ describe("Rule Loader", () => { const testRulesDir = "test/rules"; it("loads core rules", () => { - const validConfiguration: {[name: string]: any} = { - "class-name": true, - "eofline": true, - "forin": false, - "no-debugger": true, - "quotemark": [true, "single"], - }; - - const rules = loadRules(validConfiguration, {}, builtRulesDir); + const validConfiguration: IOptions[] = [ + { ruleName: "class-name", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "eofline", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "forin", ruleArguments: [], ruleSeverity: "off", disabledIntervals: [] }, + { ruleName: "no-debugger", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "quotemark", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + ]; + + const rules = loadRules(validConfiguration, new Map(), builtRulesDir); assert.equal(rules.length, 4); }); it("ignores invalid rules", () => { - const invalidConfiguration: {[name: string]: any} = { - "class-name": true, - "invalidConfig1": true, - "invalidConfig2": false, - }; + const invalidConfiguration: IOptions[] = [ + { ruleName: "class-name", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "invalidConfig1", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "invalidConfig2", ruleArguments: [], ruleSeverity: "off", disabledIntervals: [] }, + ]; - const rules = loadRules(invalidConfiguration, {}, [builtRulesDir]); + const rules = loadRules(invalidConfiguration, new Map(), [builtRulesDir]); assert.equal(rules.length, 1); }); it("properly sets rule severity with options", () => { - const withOptions = { - "callable-types": true, - "max-line-length": { - options: 140, - severity: "warning", - }, - }; - - const rules = loadRules(withOptions, {}, [builtRulesDir]); - assert.equal(rules.length, 2); - assert.equal(rules[0].getOptions().ruleSeverity, "error"); - assert.equal(rules[1].getOptions().ruleSeverity, "warning"); - }); - - it("properly sets rule severity with no options", () => { - const noOptions = { - "callable-types": true, - "interface-name": { - severity: "warning", - }, - }; + const withOptions: IOptions[] = [ + { ruleName: "callable-types", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "max-line-length", ruleArguments: [140], ruleSeverity: "warning", disabledIntervals: [] }, + ]; - const rules = loadRules(noOptions, {}, [builtRulesDir]); + const rules = loadRules(withOptions, new Map(), [builtRulesDir]); assert.equal(rules.length, 2); assert.equal(rules[0].getOptions().ruleSeverity, "error"); assert.equal(rules[1].getOptions().ruleSeverity, "warning"); }); - it("properly sets rule severity with options but no severity", () => { - const noSeverity = { - "callable-types": true, - "max-line-length": { - options: 140, - }, - }; - - const rules = loadRules(noSeverity, {}, [builtRulesDir]); - assert.equal(rules.length, 2); - assert.equal(rules[0].getOptions().ruleSeverity, "error"); - assert.equal(rules[1].getOptions().ruleSeverity, "error"); - }); - it("loads disabled rules if rule in enableDisableRuleMap", () => { - const validConfiguration: {[name: string]: any} = { - "class-name": true, - "eofline": true, - "forin": false, - "no-debugger": true, - "quotemark": [true, "single"], - }; - - const rules = loadRules(validConfiguration, {forin: [{isEnabled: true, position: 4}]}, builtRulesDir); - assert.equal(rules.length, 5); + const validConfiguration: IOptions[] = [ + { ruleName: "forin", ruleArguments: [], ruleSeverity: "off", disabledIntervals: [] }, + ]; + + const enableDisableMap = new Map(); + enableDisableMap.set("forin", [{ isEnabled: true, position: 4 }]); + const rules = loadRules(validConfiguration, enableDisableMap, builtRulesDir); + assert.equal(rules.length, 1); }); it("works with rulesDirectory argument as an Array", () => { - const validConfiguration: {[name: string]: any} = { - "class-name": true, - "eofline": true, - "forin": false, - "no-debugger": true, - "quotemark": [true, "single"], - }; - - const rules = loadRules(validConfiguration, {}, [builtRulesDir]); + const validConfiguration: IOptions[] = [ + { ruleName: "class-name", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "eofline", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "forin", ruleArguments: [], ruleSeverity: "off", disabledIntervals: [] }, + { ruleName: "no-debugger", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "quotemark", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + ]; + + const rules = loadRules(validConfiguration, new Map(), [builtRulesDir]); assert.equal(rules.length, 4); }); it("loads js rules", () => { - const validConfiguration: {[name: string]: any} = { - "class-name": true, - }; + const validConfiguration: IOptions[] = [ + { ruleName: "class-name", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + { ruleName: "await-promise", ruleArguments: [], ruleSeverity: "error", disabledIntervals: [] }, + ]; - const rules = loadRules(validConfiguration, {}, builtRulesDir, true); + const rules = loadRules(validConfiguration, new Map(), builtRulesDir, true); assert.equal(rules.length, 1); }); diff --git a/test/utils.ts b/test/utils.ts index 8aabe82f729..bc3ff88951f 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -38,9 +38,16 @@ export function getFormatter(formatterName: string) { return Lint.findFormatter(formatterName, formattersDirectory); } -export function applyRuleOnFile(fileName: string, Rule: any, ruleValue: any = true): Lint.RuleFailure[] { +// this function doesn't work with rules that use the language service +export function applyRuleOnFile(fileName: string, Rule: any, ruleArguments: any[] = []): Lint.RuleFailure[] { const sourceFile = getSourceFile(fileName); - const rule = new Rule("", ruleValue, []); + const options = { + disabledIntervals: [], + ruleArguments, + ruleName: Rule.metadata.ruleName, + ruleSeverity: "error", + }; + const rule = new Rule(options); return rule.apply(sourceFile); } From 5d21a0992d2a51074cdb5492e7314b4223587e37 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 2 Mar 2017 17:11:04 -0500 Subject: [PATCH 6/8] Remove update-notifier dependency (#2262) --- package.json | 4 +- src/runner.ts | 6 - src/updateNotifier.ts | 42 ------ yarn.lock | 291 ++++++++---------------------------------- 4 files changed, 56 insertions(+), 287 deletions(-) delete mode 100644 src/updateNotifier.ts diff --git a/package.json b/package.json index af275c8bd92..608d80bde2a 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,7 @@ "glob": "^7.1.1", "optimist": "~0.6.0", "resolve": "^1.1.7", - "tsutils": "^1.1.0", - "update-notifier": "^2.0.0" + "tsutils": "^1.1.0" }, "peerDependencies": { "typescript": ">=2.0.0 || >=2.0.0-dev || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev" @@ -62,7 +61,6 @@ "@types/node": "^6.0.56", "@types/optimist": "0.0.29", "@types/resolve": "0.0.4", - "@types/update-notifier": "^1.0.0", "chai": "^3.5.0", "github": "^8.1.1", "js-yaml": "^3.7.0", diff --git a/src/runner.ts b/src/runner.ts index deaba9a0c33..77b5b8c7560 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -29,7 +29,6 @@ import { import { FatalError } from "./error"; import * as Linter from "./linter"; import { consoleTestResultsHandler, runTests } from "./test"; -import { updateNotifierCheck } from "./updateNotifier"; export interface IRunnerOptions { /** @@ -257,10 +256,5 @@ export class Runner { onComplete(2); } }); - - if (lintResult.format === "prose") { - // Check to see if there are any updates available - updateNotifierCheck(); - } } } diff --git a/src/updateNotifier.ts b/src/updateNotifier.ts deleted file mode 100644 index 314d42bdd18..00000000000 --- a/src/updateNotifier.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright 2016 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. - */ - -import { dedent } from "./utils"; -import updateNotifier = require("update-notifier"); - -export function updateNotifierCheck(): void { - try { - const pkg = require("../package.json"); - // Check every 3 days for a new version - const cacheTime = 1000 * 60 * 60 * 24 * 3; - const changeLogUrl = "https://github.com/palantir/tslint/blob/master/CHANGELOG.md"; - const notifier = updateNotifier({ - pkg, - updateCheckInterval: cacheTime, - }); - - if (notifier.notify && notifier.update) { - notifier.notify({ - message: dedent` - TSLint update available v${notifier.update.current} → v${notifier.update.latest}. - See ${changeLogUrl}`, - }); - } - } catch (error) { - // ignore error - } -}; diff --git a/yarn.lock b/yarn.lock index f1996647356..6940b23ef39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,8 +7,8 @@ resolved "https://registry.yarnpkg.com/@types/babel-code-frame/-/babel-code-frame-6.20.0.tgz#05477b2adab210785824881481a65917335e5b68" "@types/chai@^3.4.34": - version "3.4.34" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.4.34.tgz#d5335792823bb09cddd5e38c3d211b709183854d" + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.4.35.tgz#e8d65f83492d2944f816fc620741821c28a8c900" "@types/colors@^0.6.33": version "0.6.33" @@ -44,12 +44,12 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" "@types/mocha@^2.2.35": - version "2.2.38" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.38.tgz#8c188f6e34c2e7c3f1d0127d908d5a36e5a60dc9" + version "2.2.39" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" "@types/node@*", "@types/node@^6.0.56": - version "6.0.62" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.62.tgz#85222c077b54f25b57417bb708b9f877bda37f89" + version "6.0.63" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.63.tgz#e08acbbd5946e0e95990b1c76f3ce5b7882a48eb" "@types/optimist@0.0.29": version "0.0.29" @@ -61,10 +61,6 @@ dependencies: "@types/node" "*" -"@types/update-notifier@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/update-notifier/-/update-notifier-1.0.0.tgz#3ae6206a6d67c01ffddb9a1eac4cd9b518d534ee" - agent-base@2: version "2.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.0.1.tgz#bd8f9e86a8eb221fffa07bd14befd55df142815e" @@ -120,20 +116,6 @@ balanced-match@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" - dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" - cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" - boxen@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.0.0.tgz#b2694baf1f605f708ff0177c12193b22f29aaaab" @@ -157,18 +139,10 @@ browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" -camelcase@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - camelcase@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.0.0.tgz#8b0f90d44be5e281b903b9887349b92595ef07f2" @@ -217,20 +191,6 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" - dependencies: - dot-prop "^3.0.0" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" - configstore@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.0.0.tgz#e1b8669c1803ccc50b545e92f8e6e79aa80e0196" @@ -242,11 +202,7 @@ configstore@^3.0.0: write-file-atomic "^1.1.2" xdg-basedir "^3.0.0" -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -create-error-class@^3.0.0, create-error-class@^3.0.1: +create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" dependencies: @@ -301,24 +257,12 @@ diff@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - dependencies: - is-obj "^1.0.0" - dot-prop@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.0.tgz#eb29eac57dfa31fda1edef50ea462ee3d38ff3ab" + version "4.1.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1" dependencies: is-obj "^1.0.0" -duplexer2@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -354,9 +298,9 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -esprima@^2.6.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" +esprima@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" esutils@^2.0.2: version "2.0.2" @@ -389,10 +333,6 @@ extend@3, extend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -434,8 +374,8 @@ get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" github@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/github/-/github-8.1.1.tgz#a078c61669b4d4b588bf1b2e2a591eb7c49feb36" + version "8.2.1" + resolved "https://registry.yarnpkg.com/github/-/github-8.2.1.tgz#616b2211fbcd1cc8631669aed67653e62eb53816" dependencies: follow-redirects "0.0.7" https-proxy-agent "^1.0.0" @@ -474,26 +414,6 @@ glob@~5.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" - dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" - url-parse-lax "^1.0.0" - got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -532,9 +452,15 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + hosted-git-info@^2.1.4: - version "2.1.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" + version "2.2.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.2.0.tgz#7a0d097863d886c0fabbdcd37bf1758d8becf8a5" https-proxy-agent@^1.0.0: version "1.0.0" @@ -555,7 +481,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.1: +inherits@2: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -581,12 +507,6 @@ is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -610,8 +530,10 @@ is-redirect@^1.0.0: resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" is-regex@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.3.tgz#0d55182bddf9f2fde278220aec3a75642c908637" + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" is-retry-allowed@^1.0.0: version "1.1.0" @@ -629,10 +551,6 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - isexe@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" @@ -642,11 +560,11 @@ js-tokens@^3.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" js-yaml@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + version "3.8.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.1.tgz#782ba50200be7b9e5a8537001b7804db3ad02628" dependencies: argparse "^1.0.7" - esprima "^2.6.0" + esprima "^3.1.1" json3@3.3.2: version "3.3.2" @@ -656,22 +574,12 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" - dependencies: - package-json "^2.0.0" - latest-version@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.0.0.tgz#3104f008c0c391084107f85a344bc61e38970649" dependencies: package-json "^3.0.0" -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - lazy-req@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-2.0.0.tgz#c9450a363ecdda2e6f0c70132ad4f37f8f06f2b4" @@ -800,10 +708,6 @@ netrc@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/netrc/-/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - normalize-package-data@^2.3.2: version "2.3.5" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" @@ -859,40 +763,16 @@ optimist@~0.6.0: minimist "~0.0.1" wordwrap "~0.0.2" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" - dependencies: - got "^5.0.0" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - package-json@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-3.0.0.tgz#dc11f79ebdb436e55fe2b9b294ee3f2a87a33b13" + version "3.1.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-3.1.0.tgz#ce281900fe8052150cc6709c6c006c18fdb2f379" dependencies: got "^6.7.1" registry-auth-token "^3.0.1" registry-url "^3.0.3" semver "^5.1.0" -parse-json@^2.1.0, parse-json@^2.2.0: +parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" dependencies: @@ -912,6 +792,10 @@ path-key@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -944,10 +828,6 @@ prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - ps-tree@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" @@ -959,20 +839,13 @@ pseudomap@^1.0.1: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" rc@^1.0.1, rc@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" + version "1.1.7" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea" dependencies: deep-extend "~0.4.0" ini "~1.3.0" minimist "^1.2.0" - strip-json-comments "~1.0.4" - -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" + strip-json-comments "~2.0.1" read-pkg-up@^1.0.1: version "1.0.1" @@ -989,18 +862,6 @@ read-pkg@^1.0.0, read-pkg@^1.1.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5: - version "2.2.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - registry-auth-token@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.1.0.tgz#997c08256e0c7999837b90e944db39d8a790276b" @@ -1013,19 +874,15 @@ registry-url@^3.0.3: dependencies: rc "^1.0.1" -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - dependencies: - is-finite "^1.0.0" - resolve@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" + version "1.3.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235" + dependencies: + path-parse "^1.0.5" rimraf@^2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" dependencies: glob "^7.0.5" @@ -1117,10 +974,6 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -1137,9 +990,9 @@ strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" -strip-json-comments@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" supports-color@3.1.2: version "3.1.2" @@ -1161,10 +1014,6 @@ through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" - timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -1173,8 +1022,8 @@ timed-out@^4.0.0: version "0.0.1" tslint@latest: - version "4.4.2" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.4.2.tgz#b14cb79ae039c72471ab4c2627226b940dda19c6" + version "4.5.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.5.0.tgz#12b384a339d456ee1d3cc665f13f4e759bbe5d64" dependencies: babel-code-frame "^6.20.0" colors "^1.1.2" @@ -1183,7 +1032,8 @@ tslint@latest: glob "^7.1.1" optimist "~0.6.0" resolve "^1.1.7" - update-notifier "^1.0.2" + tsutils "^1.1.0" + update-notifier "^2.0.0" tsutils@^1.1.0: version "1.1.0" @@ -1207,30 +1057,13 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" - unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" -update-notifier@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" - dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" - is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" - semver-diff "^2.0.0" - xdg-basedir "^2.0.0" - update-notifier@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.0.0.tgz#1f2712fd2079f415980b5af95eb120e4aceea9a6" + version "2.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.1.0.tgz#ec0c1e53536b76647a24b77cb83966d9315123d9" dependencies: boxen "^1.0.0" chalk "^1.0.0" @@ -1247,14 +1080,6 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -uuid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" - validate-npm-package-license@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" @@ -1290,12 +1115,6 @@ write-file-atomic@^1.1.2: imurmurhash "^0.1.4" slide "^1.1.5" -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - dependencies: - os-homedir "^1.0.0" - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" From ee721bb4fff7a3b75901cc0b29d809c157eefd5d Mon Sep 17 00:00:00 2001 From: Chris Barr Date: Fri, 3 Mar 2017 09:12:03 -0500 Subject: [PATCH 7/8] Improve the UI for the rules documentation (#2275) * Improve the UI for the rules documentation & add "feature" badges for each listed rule --- docs/_includes/rule_list.html | 19 +++++- docs/_layouts/rule.html | 18 ++++-- docs/_sass/_base.scss | 103 +++++++++++++++++++++++++++++++++ src/rules/completedDocsRule.ts | 4 +- 4 files changed, 136 insertions(+), 8 deletions(-) diff --git a/docs/_includes/rule_list.html b/docs/_includes/rule_list.html index dd990de3029..dd4ee71c380 100644 --- a/docs/_includes/rule_list.html +++ b/docs/_includes/rule_list.html @@ -1,6 +1,21 @@ -
    + \ No newline at end of file diff --git a/docs/_layouts/rule.html b/docs/_layouts/rule.html index de7b8ca3c8c..9299ab37db1 100644 --- a/docs/_layouts/rule.html +++ b/docs/_layouts/rule.html @@ -9,11 +9,21 @@
    Rationale
    {{page.rationale | markdownify}} {% endif %} -{% if page.requiresTypeInfo %} - Note: - This rule requires type info to run -{% endif %} +{% if page.typescriptOnly or page.hasFix or page.requiresTypeInfo %} +
    Notes:
    +
    + {% if page.typescriptOnly %} + TS Only + {% endif %} + {% if page.hasFix %} + Has Fixer + {% endif %} + {% if page.requiresTypeInfo %} + Requires Type Info + {% endif %} +
    +{% endif %}

    Config

    {{page.optionsDescription | markdownify}} diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss index 52b4aea6e1d..52e6af050e6 100644 --- a/docs/_sass/_base.scss +++ b/docs/_sass/_base.scss @@ -70,3 +70,106 @@ figcaption { } } } + + +/** + * Rules & Feature Badges + */ + .rules-list { + list-style: none; + margin: 0 !important; //need to override the `main-content ul` selector + + > li { + &:nth-child(odd) { + a { + background-color: rgba(0, 0, 0, .03); + } + } + + a { + display: block; + border-left: 3px solid transparent; + text-decoration: none; + padding: .75rem; + + &:hover { + background-color: rgba(0, 0, 0,.075); + border-left-color: #159957; + } + } + } + } + + .rule-features { + //This is the container for a list of feature badges + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + } + + .feature { + //This is the setup for the a feature badge + display: inline-block; + margin-right: 2px; + padding: 2px 4px; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border: 1px solid transparent; + border-radius: .25rem; + cursor: help; + + &:before { + //This is the setup for the icon that appears inside the badge + display: inline-block; + margin-right: 2px; + } + + &.feature-sm { + //This class is added to make the feature badge smaller. This is used on the rules list + padding: 1px 3px; + font-size: 75%; + } + + &.feature-ts-only { + //This feature badge is added to rules that are "TypeScript Only" + background-color: #FCF8E3; + border-color: #FAF2CC; + color: #8A6D3B; + + &:before { + content: "\1F4C4"; //"page facing up" icon - http://www.fileformat.info/info/unicode/char/1F4C4/index.htm + } + } + + &.feature-fixer { + //This feature badge is added to rules that have an auto-fixer + background-color: #DFF0D8; + border-color: #D0E9C6; + color: #3C763D; + + &:before { + content: "\1f527"; //"wrench" icon - http://www.fileformat.info/info/unicode/char/1f527/index.htm + } + } + + &.feature-requires-type-info { + //This feature badge is added to rules that require type information + background-color: #F2DEDE; + border-color: #EBCCCC; + color: #A94442; + + &:before { + content: "\2139"; //"information source" icon - http://www.fileformat.info/info/unicode/char/2139/index.htm + //Surround it with a blue circle + border-radius: 50%; + background: #0078D7; + color: #FFF; + width: 1em; + } + } + } diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index 17dc0a44584..fa3847f250e 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -140,8 +140,8 @@ export class Rule extends Lint.Rules.TypedRule { ruleName: "completed-docs", description: "Enforces documentation for important items be filled out.", optionsDescription: Lint.Utils.dedent` - \`true\` to enable for ["${ARGUMENT_CLASSES}", "${ARGUMENT_FUNCTIONS}", "${ARGUMENT_METHODS}", "${ARGUMENT_PROPERTIES}"], - or an array with each item in one of two formats: + \`true\` to enable for ["${ARGUMENT_CLASSES}", "${ARGUMENT_FUNCTIONS}", "${ARGUMENT_METHODS}", "${ARGUMENT_PROPERTIES}"], + or an array with each item in one of two formats: * \`string\` to enable for that type * \`object\` keying types to when their documentation is required: From 3ea76fa0c09136a54880e754bd0ce8b485bc5105 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 3 Mar 2017 11:19:02 -0500 Subject: [PATCH 8/8] reduce containers back to 4 (#2292) --- circle.yml | 4 ++-- scripts/assertMinCircleNodes.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/circle.yml b/circle.yml index 363c0bcdcc7..6f0627e6a30 100644 --- a/circle.yml +++ b/circle.yml @@ -5,12 +5,12 @@ general: dependencies: pre: - node ./scripts/assertMinCircleNodes.js $CIRCLE_NODE_TOTAL - - case $CIRCLE_NODE_INDEX in 0) nvm use 4.1.2 ;; 1) nvm use 5.7 ;; [2-4]) nvm use 6.1 ;; esac + - case $CIRCLE_NODE_INDEX in 0) nvm use 4.1.2 ;; 1) nvm use 5.7 ;; [2-3]) nvm use 6.1 ;; esac override: - yarn test: override: - - case $CIRCLE_NODE_INDEX in [0-2]) yarn verify ;; 3) npm run clean && yarn compile && yarn add typescript@2.0.10 && yarn test ;; 4) npm run clean && yarn compile && yarn add typescript@next && yarn test ;; esac: + - case $CIRCLE_NODE_INDEX in [0-1]) yarn verify ;; 2) npm run clean && yarn compile && yarn add typescript@2.0.10 && yarn test ;; 3) npm run clean && yarn compile && yarn add typescript@next && yarn test ;; esac: parallel: true deployment: npm-latest: diff --git a/scripts/assertMinCircleNodes.js b/scripts/assertMinCircleNodes.js index ef418463d1a..bf73b44cc9c 100644 --- a/scripts/assertMinCircleNodes.js +++ b/scripts/assertMinCircleNodes.js @@ -1,4 +1,4 @@ -var requiredNodes = 5; +var requiredNodes = 4; var nodes = parseInt(process.argv[2], 10); if (requiredNodes != null && requiredNodes > nodes) { console.error("ERROR: You must run CircleCI with " + requiredNodes + " parallel nodes");