diff --git a/@commitlint/cli/cli.js b/@commitlint/cli/cli.js old mode 100644 new mode 100755 diff --git a/@commitlint/cli/src/cli.ts b/@commitlint/cli/src/cli.ts index 18a2eb7545..1d761f1d06 100644 --- a/@commitlint/cli/src/cli.ts +++ b/@commitlint/cli/src/cli.ts @@ -13,7 +13,8 @@ import { LintOutcome, ParserOptions, ParserPreset, - QualifiedConfig + QualifiedConfig, + Formatter } from '@commitlint/types'; import {CliError} from './cli-error'; @@ -177,7 +178,7 @@ async function main(options: CliFlags) { const results = await Promise.all( // TODO: validate why those types do not match - messages.map(message => lint(message, loaded.rules as any, opts)) + messages.map(message => lint(message, loaded.rules, opts)) ); if (Object.keys(loaded.rules).length === 0) { @@ -338,10 +339,8 @@ function selectParserOpts(parserPreset: ParserPreset) { return parserPreset.parserOpts; } -function loadFormatter(config: QualifiedConfig, flags: CliFlags) { - // TODO: validate why formatter is unknown???? - const moduleName: string = - flags.format || (config.formatter as any) || '@commitlint/format'; +function loadFormatter(config: QualifiedConfig, flags: CliFlags): Formatter { + const moduleName = flags.format || config.formatter || '@commitlint/format'; const modulePath = resolveFrom.silent(__dirname, moduleName) || resolveFrom.silent(flags.cwd, moduleName) || diff --git a/@commitlint/format/package.json b/@commitlint/format/package.json index d8d8201f69..ceb59d27f5 100644 --- a/@commitlint/format/package.json +++ b/@commitlint/format/package.json @@ -37,6 +37,7 @@ "@commitlint/utils": "^8.3.4" }, "dependencies": { + "@commitlint/types": "^8.3.5", "chalk": "^3.0.0" } } diff --git a/@commitlint/format/src/format.ts b/@commitlint/format/src/format.ts index 050c8fba63..cf75cf824d 100644 --- a/@commitlint/format/src/format.ts +++ b/@commitlint/format/src/format.ts @@ -1,41 +1,19 @@ import chalk from 'chalk'; +import { + ChalkColor, + FormattableReport, + FormatOptions, + FormattableResult, + WithInput +} from '@commitlint/types'; const DEFAULT_SIGNS = [' ', '⚠', '✖'] as const; const DEFAULT_COLORS = ['white', 'yellow', 'red'] as const; -export interface FormattableProblem { - level: 0 | 1 | 2; - name: string; - message: string; -} - -export interface FormattableResult { - errors?: FormattableProblem[]; - warnings?: FormattableProblem[]; -} - -export interface WithInput { - input?: string; -} - -export interface FormattableReport { - results?: (FormattableResult & WithInput)[]; -} - -export type ChalkColor = keyof typeof chalk; - -export interface FormatOptions { - color?: boolean; - signs?: readonly [string, string, string]; - colors?: readonly [ChalkColor, ChalkColor, ChalkColor]; - verbose?: boolean; - helpUrl?: string; -} - export function format( report: FormattableReport = {}, options: FormatOptions = {} -) { +): string { const {results = []} = report; const fi = (result: FormattableResult & WithInput) => formatInput(result, options); diff --git a/@commitlint/format/tsconfig.json b/@commitlint/format/tsconfig.json index 49479bf34f..119e645565 100644 --- a/@commitlint/format/tsconfig.json +++ b/@commitlint/format/tsconfig.json @@ -6,5 +6,6 @@ "outDir": "./lib" }, "include": ["./src"], - "exclude": ["./src/**/*.test.ts", "./lib/**/*"] + "exclude": ["./src/**/*.test.ts", "./lib/**/*"], + "references": [{"path": "../types"}] } diff --git a/@commitlint/lint/src/lint.test.ts b/@commitlint/lint/src/lint.test.ts index c8ff6cc123..521f826b4a 100644 --- a/@commitlint/lint/src/lint.test.ts +++ b/@commitlint/lint/src/lint.test.ts @@ -125,8 +125,8 @@ test('throws for rule with invalid level', async () => { test('throws for rule with out of range level', async () => { const error = lint('type(scope): foo', { - 'type-enum': [-1, 'always'], - 'header-max-length': [3, 'always'] + 'type-enum': [-1, 'always'] as any, + 'header-max-length': [3, 'always'] as any }); await expect(error).rejects.toThrow('rule type-enum must be between 0 and 2'); diff --git a/@commitlint/lint/src/lint.ts b/@commitlint/lint/src/lint.ts index 3807afe73e..8df8cd77a2 100644 --- a/@commitlint/lint/src/lint.ts +++ b/@commitlint/lint/src/lint.ts @@ -4,17 +4,17 @@ import parse from '@commitlint/parse'; import defaultRules from '@commitlint/rules'; import {buildCommitMesage} from './commit-message'; import { - LintRuleConfig, LintOptions, LintOutcome, LintRuleOutcome, Rule, - RuleSeverity + RuleConfigSeverity, + QualifiedRules } from '@commitlint/types'; export default async function lint( message: string, - rawRulesConfig?: LintRuleConfig, + rawRulesConfig?: QualifiedRules, rawOpts?: LintOptions ): Promise { const opts = rawOpts @@ -76,7 +76,7 @@ export default async function lint( const [level] = config; - if (level === RuleSeverity.Disabled && config.length === 1) { + if (level === RuleConfigSeverity.Disabled && config.length === 1) { return null; } @@ -132,10 +132,10 @@ export default async function lint( // Validate against all rules const results = Object.entries(rulesConfig) - .filter(([, [level]]) => level > 0) + .filter(([, config]) => typeof config !== 'undefined' && config[0] > 0) .map(entry => { const [name, config] = entry; - const [level, when, value] = config; + const [level, when, value] = config!; // // Level 0 rules are ignored if (level === 0) { @@ -148,8 +148,7 @@ export default async function lint( throw new Error(`Could not find rule implementation for ${name}`); } - const executableRule = rule as Rule; - const [valid, message] = executableRule(parsed, when, value); + const [valid, message] = rule(parsed, when, value as any); return { level, diff --git a/@commitlint/types/src/format.ts b/@commitlint/types/src/format.ts new file mode 100644 index 0000000000..ba50d8a79f --- /dev/null +++ b/@commitlint/types/src/format.ts @@ -0,0 +1,37 @@ +import chalk from 'chalk'; +import {QualifiedRules} from './load'; +import {RuleConfigSeverity} from './rules'; + +export type Formatter = ( + report: FormattableReport, + options: FormatOptions +) => string; + +export interface FormattableProblem { + level: RuleConfigSeverity; + name: keyof QualifiedRules; + message: string; +} + +export interface FormattableResult { + errors?: FormattableProblem[]; + warnings?: FormattableProblem[]; +} + +export interface WithInput { + input?: string; +} + +export interface FormattableReport { + results?: (FormattableResult & WithInput)[]; +} + +export type ChalkColor = keyof typeof chalk; + +export interface FormatOptions { + color?: boolean; + signs?: readonly [string, string, string]; + colors?: readonly [ChalkColor, ChalkColor, ChalkColor]; + verbose?: boolean; + helpUrl?: string; +} diff --git a/@commitlint/types/src/index.ts b/@commitlint/types/src/index.ts index f562854fed..562a9a6373 100644 --- a/@commitlint/types/src/index.ts +++ b/@commitlint/types/src/index.ts @@ -1,4 +1,5 @@ export * from './ensure'; +export * from './format'; export * from './is-ignored'; export * from './rules'; export * from './lint'; diff --git a/@commitlint/types/src/lint.ts b/@commitlint/types/src/lint.ts index 6ebf81e448..e9f047e9e0 100644 --- a/@commitlint/types/src/lint.ts +++ b/@commitlint/types/src/lint.ts @@ -1,10 +1,11 @@ import {IsIgnoredOptions} from './is-ignored'; -import {RuleConfigTuple, PluginRecords, RuleSeverity} from './load'; +import {PluginRecords} from './load'; import {ParserOptions} from './parse'; +import {RuleConfigSeverity, RuleConfigTuple} from './rules'; export type LintRuleConfig = Record< string, - | Readonly<[RuleSeverity.Disabled]> + | Readonly<[RuleConfigSeverity.Disabled]> | RuleConfigTuple | RuleConfigTuple >; @@ -35,7 +36,7 @@ export interface LintRuleOutcome { /** If the commit is considered valid for the rule */ valid: boolean; /** The "severity" of the rule (1 = warning, 2 = error) */ - level: RuleSeverity; + level: RuleConfigSeverity; /** The name of the rule */ name: string; /** The message returned from the rule, if invalid */ diff --git a/@commitlint/types/src/load.ts b/@commitlint/types/src/load.ts index 7779136556..25c05d7aff 100644 --- a/@commitlint/types/src/load.ts +++ b/@commitlint/types/src/load.ts @@ -1,5 +1,4 @@ -import {TargetCaseType} from './ensure'; -import {Rule, RuleCondition} from './rules'; +import {Rule, RulesConfig, RuleConfigQuality} from './rules'; export type PluginRecords = Record; @@ -14,84 +13,9 @@ export interface LoadOptions { file?: string; } -export enum RuleSeverity { - Disabled = 0, - Warning = 1, - Error = 2 -} - -export type RuleConfigTuple = T extends void - ? Readonly<[RuleSeverity, RuleCondition]> - : Readonly<[RuleSeverity, RuleCondition, T]>; - -export enum RuleConfigQuality { - User, - Qualified -} - -export type QualifiedRuleConfig = - | (() => RuleConfigTuple) - | (() => RuleConfigTuple>) - | RuleConfigTuple; - -export type RuleConfig< - V = RuleConfigQuality.Qualified, - T = void -> = V extends RuleConfigQuality.Qualified - ? RuleConfigTuple - : QualifiedRuleConfig; - -export type CaseRuleConfig = RuleConfig< - V, - TargetCaseType ->; -export type LengthRuleConfig = RuleConfig< - V, - number ->; -export type EnumRuleConfig = RuleConfig< - V, - string[] ->; - -export type RulesConfig = { - 'body-case': CaseRuleConfig; - 'body-empty': RuleConfig; - 'body-leading-blank': RuleConfig; - 'body-max-length': LengthRuleConfig; - 'body-max-line-length': LengthRuleConfig; - 'body-min-length': LengthRuleConfig; - 'footer-empty': RuleConfig; - 'footer-leading-blank': RuleConfig; - 'footer-max-length': LengthRuleConfig; - 'footer-max-line-length': LengthRuleConfig; - 'footer-min-length': LengthRuleConfig; - 'header-case': CaseRuleConfig; - 'header-full-stop': RuleConfig; - 'header-max-length': LengthRuleConfig; - 'header-min-length': LengthRuleConfig; - 'references-empty': RuleConfig; - 'scope-case': CaseRuleConfig; - 'scope-empty': RuleConfig; - 'scope-enum': EnumRuleConfig; - 'scope-max-length': LengthRuleConfig; - 'scope-min-length': LengthRuleConfig; - 'signed-off-by': RuleConfig; - 'subject-case': CaseRuleConfig; - 'subject-empty': RuleConfig; - 'subject-full-stop': RuleConfig; - 'subject-max-length': LengthRuleConfig; - 'subject-min-length': LengthRuleConfig; - 'type-case': CaseRuleConfig; - 'type-empty': RuleConfig; - 'type-enum': EnumRuleConfig; - 'type-max-length': LengthRuleConfig; - 'type-min-length': LengthRuleConfig; -}; - export interface UserConfig { extends?: string[]; - formatter?: unknown; + formatter?: string; rules?: Partial; parserPreset?: string | ParserPreset; ignores?: ((commit: string) => boolean)[]; @@ -101,7 +25,7 @@ export interface UserConfig { export interface UserPreset { extends?: string[]; - formatter?: unknown; + formatter?: string; rules?: Partial; parserPreset?: string | ParserPreset; ignores?: ((commit: string) => boolean)[]; @@ -113,7 +37,7 @@ export type QualifiedRules = Partial>; export interface QualifiedConfig { extends: string[]; - formatter: unknown; + formatter: string; rules: QualifiedRules; parserPreset: ParserPreset; ignores: ((commit: string) => boolean)[]; diff --git a/@commitlint/types/src/rules.ts b/@commitlint/types/src/rules.ts index b064cb4d32..73d0c62d2f 100644 --- a/@commitlint/types/src/rules.ts +++ b/@commitlint/types/src/rules.ts @@ -1,12 +1,6 @@ +import {TargetCaseType} from './ensure'; import {Commit} from './parse'; -/** - * Rules always have a condition. - * It can be either "always" (as tested), or "never" (as tested). - * For example, `header-full-stop` can be enforced as "always" or "never". - */ -export type RuleCondition = 'always' | 'never'; - /** * Rules match the input either as successful or failed. * For example, when `header-full-stop` detects a full stop and is set as "always"; it's true. @@ -20,6 +14,103 @@ export type RuleOutcome = [boolean, string?]; */ export type Rule = ( parsed: Commit, - when?: RuleCondition, + when?: RuleConfigCondition, value?: Value ) => RuleOutcome; + +/** + * Rules always have a severity. + * Severity indicates what to do if the rule is found to be broken + * 0 - Disable this rule + * 1 - Warn for violations + * 2 - Error for violations + */ +export enum RuleConfigSeverity { + Disabled = 0, + Warning = 1, + Error = 2 +} + +/** + * Rules always have a condition. + * It can be either "always" (as tested), or "never" (as tested). + * For example, `header-full-stop` can be enforced as "always" or "never". + */ +export type RuleConfigCondition = 'always' | 'never'; + +export type RuleConfigTuple = T extends void + ? + | Readonly<[RuleConfigSeverity.Disabled]> + | Readonly<[RuleConfigSeverity, RuleConfigCondition]> + : + | Readonly<[RuleConfigSeverity.Disabled]> + | Readonly<[RuleConfigSeverity, RuleConfigCondition, T]>; + +export enum RuleConfigQuality { + User, + Qualified +} + +export type QualifiedRuleConfig = + | (() => RuleConfigTuple) + | (() => RuleConfigTuple>) + | RuleConfigTuple; + +export type RuleConfig< + V = RuleConfigQuality.Qualified, + T = void +> = V extends RuleConfigQuality.Qualified + ? RuleConfigTuple + : QualifiedRuleConfig; + +export type CaseRuleConfig = RuleConfig< + V, + TargetCaseType +>; +export type LengthRuleConfig = RuleConfig< + V, + number +>; +export type EnumRuleConfig = RuleConfig< + V, + string[] +>; + +export type RulesConfig = { + 'body-case': CaseRuleConfig; + 'body-empty': RuleConfig; + 'body-leading-blank': RuleConfig; + 'body-max-length': LengthRuleConfig; + 'body-max-line-length': LengthRuleConfig; + 'body-min-length': LengthRuleConfig; + 'footer-empty': RuleConfig; + 'footer-leading-blank': RuleConfig; + 'footer-max-length': LengthRuleConfig; + 'footer-max-line-length': LengthRuleConfig; + 'footer-min-length': LengthRuleConfig; + 'header-case': CaseRuleConfig; + 'header-full-stop': RuleConfig; + 'header-max-length': LengthRuleConfig; + 'header-min-length': LengthRuleConfig; + 'references-empty': RuleConfig; + 'scope-case': CaseRuleConfig; + 'scope-empty': RuleConfig; + 'scope-enum': EnumRuleConfig; + 'scope-max-length': LengthRuleConfig; + 'scope-min-length': LengthRuleConfig; + 'signed-off-by': RuleConfig; + 'subject-case': CaseRuleConfig; + 'subject-empty': RuleConfig; + 'subject-full-stop': RuleConfig; + 'subject-max-length': LengthRuleConfig; + 'subject-min-length': LengthRuleConfig; + 'type-case': CaseRuleConfig; + 'type-empty': RuleConfig; + 'type-enum': EnumRuleConfig; + 'type-max-length': LengthRuleConfig; + 'type-min-length': LengthRuleConfig; + // Plugins may add their custom rules + [key: string]: AnyRuleConfig; +}; + +export type AnyRuleConfig = RuleConfig | RuleConfig; diff --git a/yarn.lock b/yarn.lock index 4d9a5458b5..fc08bf2455 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1893,6 +1893,13 @@ resolved "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/execa@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@types/execa/-/execa-0.9.0.tgz#9b025d2755f17e80beaf9368c3f4f319d8b0fb93" + integrity sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA== + dependencies: + "@types/node" "*" + "@types/fs-extra@^8.0.1": version "8.0.1" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.1.tgz#a2378d6e7e8afea1564e44aafa2e207dadf77686" @@ -8571,7 +8578,7 @@ resolve@1.1.7: resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.12.0, resolve@^1.13.1: +resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.8.1: version "1.15.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==