Skip to content

Commit 6169610

Browse files
committed
refactor: port to ts
1 parent 7bf13b4 commit 6169610

File tree

9 files changed

+108
-71
lines changed

9 files changed

+108
-71
lines changed

@commitlint/lint/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"name": "@commitlint/lint",
33
"version": "8.3.5",
44
"description": "Lint a string against commitlint rules",
5-
"main": "lib/index.js",
6-
"types": "lib/index.d.ts",
5+
"main": "lib/lint.js",
6+
"types": "lib/lint.d.ts",
77
"files": [
88
"lib/"
99
],
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface CommitMessageData {
2+
header: string;
3+
body?: string | null;
4+
footer?: string | null;
5+
}
6+
7+
export const buildCommitMesage = ({
8+
header,
9+
body,
10+
footer
11+
}: CommitMessageData): string => {
12+
let message = header;
13+
14+
message = body ? `${message}\n\n${body}` : message;
15+
message = footer ? `${message}\n\n${footer}` : message;
16+
17+
return message;
18+
};

@commitlint/lint/src/index.test.js renamed to @commitlint/lint/src/lint.test.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import lint from '.';
1+
import lint from './lint';
22

33
test('throws without params', async () => {
4-
const error = lint();
4+
const error = (lint as any)();
55
await expect(error).rejects.toThrow('Expected a raw commit');
66
});
77

88
test('throws with empty message', async () => {
9-
const error = lint('');
9+
const error = (lint as any)('');
1010
await expect(error).rejects.toThrow('Expected a raw commit');
1111
});
1212

@@ -91,7 +91,7 @@ test('throws for invalid rule config', async () => {
9191
const error = lint('type(scope): foo', {
9292
'type-enum': 1,
9393
'scope-enum': {0: 2, 1: 'never', 2: ['foo'], length: 3}
94-
});
94+
} as any);
9595

9696
await expect(error).rejects.toThrow('type-enum must be array');
9797
await expect(error).rejects.toThrow('scope-enum must be array');
@@ -109,15 +109,15 @@ test('allows disable shorthand', async () => {
109109
});
110110

111111
test('throws for rule with invalid length', async () => {
112-
const error = lint('type(scope): foo', {'scope-enum': [1, 2, 3, 4]});
112+
const error = lint('type(scope): foo', {'scope-enum': [1, 2, 3, 4]} as any);
113113

114114
await expect(error).rejects.toThrow('scope-enum must be 2 or 3 items long');
115115
});
116116

117117
test('throws for rule with invalid level', async () => {
118118
const error = lint('type(scope): foo', {
119-
'type-enum': ['2', 'always'],
120-
'header-max-length': [{}, 'always']
119+
'type-enum': ['2', 'always'] as any,
120+
'header-max-length': [{}, 'always'] as any
121121
});
122122
await expect(error).rejects.toThrow('rule type-enum must be number');
123123
await expect(error).rejects.toThrow('rule header-max-length must be number');
@@ -137,8 +137,8 @@ test('throws for rule with out of range level', async () => {
137137

138138
test('throws for rule with invalid condition', async () => {
139139
const error = lint('type(scope): foo', {
140-
'type-enum': [1, 2],
141-
'header-max-length': [1, {}]
140+
'type-enum': [1, 2] as any,
141+
'header-max-length': [1, {}] as any
142142
});
143143

144144
await expect(error).rejects.toThrow('type-enum must be string');
@@ -147,8 +147,8 @@ test('throws for rule with invalid condition', async () => {
147147

148148
test('throws for rule with out of range condition', async () => {
149149
const error = lint('type(scope): foo', {
150-
'type-enum': [1, 'foo'],
151-
'header-max-length': [1, 'bar']
150+
'type-enum': [1, 'foo'] as any,
151+
'header-max-length': [1, 'bar'] as any
152152
});
153153

154154
await expect(error).rejects.toThrow('type-enum must be "always" or "never"');

@commitlint/lint/src/index.js renamed to @commitlint/lint/src/lint.ts

+44-35
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import util from 'util';
22
import isIgnored from '@commitlint/is-ignored';
33
import parse from '@commitlint/parse';
4-
import implementations from '@commitlint/rules';
4+
import defaultRules, {Rule} from '@commitlint/rules';
55
import toPairs from 'lodash/toPairs';
66
import values from 'lodash/values';
7+
import {buildCommitMesage} from './commit-message';
8+
import {LintRuleConfig, LintOptions, LintRuleOutcome} from './types';
9+
import {Plugin, RuleSeverity} from '@commitlint/load';
10+
11+
export default async function lint(
12+
message: string,
13+
rawRulesConfig?: LintRuleConfig,
14+
rawOpts?: LintOptions
15+
) {
16+
const opts = rawOpts
17+
? rawOpts
18+
: {defaultIgnores: undefined, ignores: undefined};
19+
const rulesConfig = rawRulesConfig || {};
720

8-
const buildCommitMesage = ({header, body, footer}) => {
9-
let message = header;
10-
11-
message = body ? `${message}\n\n${body}` : message;
12-
message = footer ? `${message}\n\n${footer}` : message;
13-
14-
return message;
15-
};
16-
17-
export default async (message, rules = {}, opts = {}) => {
1821
// Found a wildcard match, skip
1922
if (
2023
isIgnored(message, {defaults: opts.defaultIgnores, ignores: opts.ignores})
@@ -29,33 +32,35 @@ export default async (message, rules = {}, opts = {}) => {
2932

3033
// Parse the commit message
3134
const parsed = await parse(message, undefined, opts.parserOpts);
35+
const allRules: Map<string, Rule<unknown> | Rule<never>> = new Map(
36+
Object.entries(defaultRules)
37+
);
3238

33-
const mergedImplementations = Object.assign({}, implementations);
3439
if (opts.plugins) {
35-
values(opts.plugins).forEach(plugin => {
40+
values(opts.plugins).forEach((plugin: Plugin) => {
3641
if (plugin.rules) {
37-
Object.keys(plugin.rules).forEach(ruleKey => {
38-
mergedImplementations[ruleKey] = plugin.rules[ruleKey];
39-
});
42+
Object.keys(plugin.rules).forEach(ruleKey =>
43+
allRules.set(ruleKey, plugin.rules[ruleKey])
44+
);
4045
}
4146
});
4247
}
4348

4449
// Find invalid rules configs
45-
const missing = Object.keys(rules).filter(
46-
name => typeof mergedImplementations[name] !== 'function'
50+
const missing = Object.keys(rulesConfig).filter(
51+
name => typeof allRules.get(name) !== 'function'
4752
);
4853

4954
if (missing.length > 0) {
50-
const names = Object.keys(mergedImplementations);
55+
const names = [...allRules.keys()];
5156
throw new RangeError(
5257
`Found invalid rule names: ${missing.join(
5358
', '
5459
)}. Supported rule names are: ${names.join(', ')}`
5560
);
5661
}
5762

58-
const invalid = toPairs(rules)
63+
const invalid = toPairs(rulesConfig)
5964
.map(([name, config]) => {
6065
if (!Array.isArray(config)) {
6166
return new Error(
@@ -65,7 +70,13 @@ export default async (message, rules = {}, opts = {}) => {
6570
);
6671
}
6772

68-
const [level, when] = config;
73+
const [level] = config;
74+
75+
if (level === RuleSeverity.Disabled && config.length === 1) {
76+
return null;
77+
}
78+
79+
const [, when] = config;
6980

7081
if (typeof level !== 'number' || isNaN(level)) {
7182
return new Error(
@@ -75,10 +86,6 @@ export default async (message, rules = {}, opts = {}) => {
7586
);
7687
}
7788

78-
if (level === 0 && config.length === 1) {
79-
return null;
80-
}
81-
8289
if (config.length !== 2 && config.length !== 3) {
8390
return new Error(
8491
`config for rule ${name} must be 2 or 3 items long, received ${util.inspect(
@@ -113,18 +120,15 @@ export default async (message, rules = {}, opts = {}) => {
113120

114121
return null;
115122
})
116-
.filter(item => item instanceof Error);
123+
.filter((item): item is Error => item instanceof Error);
117124

118125
if (invalid.length > 0) {
119126
throw new Error(invalid.map(i => i.message).join('\n'));
120127
}
121128

122129
// Validate against all rules
123-
const results = toPairs(rules)
124-
.filter(entry => {
125-
const [, [level]] = toPairs(entry);
126-
return level > 0;
127-
})
130+
const results = toPairs(rulesConfig)
131+
.filter(([, [level]]) => level > 0)
128132
.map(entry => {
129133
const [name, config] = entry;
130134
const [level, when, value] = config;
@@ -134,9 +138,14 @@ export default async (message, rules = {}, opts = {}) => {
134138
return null;
135139
}
136140

137-
const rule = mergedImplementations[name];
141+
const rule = allRules.get(name);
142+
143+
if (!rule) {
144+
throw new Error(`Could not find rule implementation for ${name}`);
145+
}
138146

139-
const [valid, message] = rule(parsed, when, value);
147+
const executableRule = rule as Rule<unknown>;
148+
const [valid, message] = executableRule(parsed, when, value);
140149

141150
return {
142151
level,
@@ -145,7 +154,7 @@ export default async (message, rules = {}, opts = {}) => {
145154
message
146155
};
147156
})
148-
.filter(Boolean);
157+
.filter((result): result is LintRuleOutcome => result !== null);
149158

150159
const errors = results.filter(result => result.level === 2 && !result.valid);
151160
const warnings = results.filter(
@@ -160,4 +169,4 @@ export default async (message, rules = {}, opts = {}) => {
160169
warnings,
161170
input: buildCommitMesage(parsed)
162171
};
163-
};
172+
}

@commitlint/lint/src/types.ts

+10-13
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
import {IsIgnoredOptions} from '@commitlint/is-ignored';
2+
import {RuleConfigTuple, PluginRecords, RuleSeverity} from '@commitlint/load';
23
import {ParserOptions} from '@commitlint/parse';
3-
import {Rule} from '@commitlint/rules';
44

5-
export type Linter = (
6-
commit: string,
7-
config: LinterRuleConfig,
8-
options: LinterOptions
9-
) => Promise<LintOutcome>;
5+
export type LintRuleConfig = Record<
6+
string,
7+
| Readonly<[RuleSeverity.Disabled]>
8+
| RuleConfigTuple<void>
9+
| RuleConfigTuple<unknown>
10+
>;
1011

11-
export type LinterRuleConfig = {
12-
[key: string]: any; // todo: use rule configuration from `@commitlint/load`
13-
};
14-
15-
export interface LinterOptions {
12+
export interface LintOptions {
1613
/** If it should ignore the default commit messages (defaults to `true`) */
1714
defaultIgnores?: IsIgnoredOptions['defaults'];
1815
/** Additional commits to ignore, defined by ignore matchers */
1916
ignores?: IsIgnoredOptions['ignores'];
2017
/** The parser configuration to use when linting the commit */
2118
parserOpts?: ParserOptions;
2219

23-
plugins?: any; // todo: reuse types from `@commitlint/load`
20+
plugins?: PluginRecords;
2421
}
2522

2623
export interface LintOutcome {
@@ -42,5 +39,5 @@ export interface LintRuleOutcome {
4239
/** The name of the rule */
4340
name: string;
4441
/** The message returned from the rule, if invalid */
45-
message?: string;
42+
message: string;
4643
}

@commitlint/load/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"name": "@commitlint/load",
33
"version": "8.3.5",
44
"description": "Load shared commitlint configuration",
5-
"main": "lib/load.js",
6-
"types": "lib/load.d.ts",
5+
"main": "lib/index.js",
6+
"types": "lib/index.d.ts",
77
"files": [
88
"lib/"
99
],

@commitlint/load/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import load from './load';
2+
export default load;
3+
4+
export * from './types';

@commitlint/load/src/types.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import {TargetCaseType} from '@commitlint/ensure';
2+
import {Rule} from '@commitlint/rules';
23

34
export type RuleCondition = 'always' | 'never';
45

5-
export type PluginRecords = Record<string, unknown>;
6+
export type PluginRecords = Record<string, Plugin>;
7+
8+
export interface Plugin {
9+
rules: {
10+
[ruleName: string]: Rule<unknown>;
11+
};
12+
}
613

714
export interface LoadOptions {
815
cwd?: string;
916
file?: string;
1017
}
1118

1219
export enum RuleSeverity {
20+
Disabled = 0,
1321
Warning = 1,
1422
Error = 2
1523
}
1624

17-
export type RuleConfigTuple<T> = ReadonlyArray<
18-
T extends void
19-
? [RuleSeverity, RuleCondition]
20-
: [RuleSeverity, RuleCondition, T]
21-
>;
25+
export type RuleConfigTuple<T> = T extends void
26+
? Readonly<[RuleSeverity, RuleCondition]>
27+
: Readonly<[RuleSeverity, RuleCondition, T]>;
2228

2329
export enum RuleConfigQuality {
2430
User,
@@ -33,7 +39,9 @@ export type QualifiedRuleConfig<T> =
3339
export type RuleConfig<
3440
V = RuleConfigQuality.Qualified,
3541
T = void
36-
> = V extends false ? RuleConfigTuple<T> : QualifiedRuleConfig<T>;
42+
> = V extends RuleConfigQuality.Qualified
43+
? RuleConfigTuple<T>
44+
: QualifiedRuleConfig<T>;
3745

3846
export type CaseRuleConfig<V = RuleConfigQuality.User> = RuleConfig<
3947
V,

tsconfig.shared.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"keyofStringsOnly": true,
1919
"noFallthroughCasesInSwitch": true,
2020
"isolatedModules": true,
21-
"skipLibCheck": true
21+
"skipLibCheck": true,
22+
"downlevelIteration": true
2223
}
2324
}

0 commit comments

Comments
 (0)