Skip to content

Commit 935bc10

Browse files
byCedricmarionebl
andauthored
refactor: port lint to typescript (#908)
* chore(lint): add first draft of lint types * chore(lint): update package settings for typescript * fix(is-ignored): explicitly export default from is ignored * docs(lint): add todo message for plugin types * refactor: port to ts * fix: add missing references * refactor: move shared types to package * fix: streamline verison * refactor: reuse RuleSeverity Co-authored-by: Mario Nebl <[email protected]>
1 parent 830c343 commit 935bc10

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+300
-151
lines changed

@commitlint/ensure/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"globby": "11.0.0"
3939
},
4040
"dependencies": {
41+
"@commitlint/types": "^8.3.4",
4142
"lodash": "^4.17.15"
4243
}
4344
}

@commitlint/ensure/src/case.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import kebabCase from 'lodash/kebabCase';
33
import snakeCase from 'lodash/snakeCase';
44
import upperFirst from 'lodash/upperFirst';
55
import startCase from 'lodash/startCase';
6-
import {TargetCaseType} from '.';
6+
import {TargetCaseType} from '@commitlint/types';
77

88
export default ensureCase;
99

@commitlint/ensure/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import maxLineLength from './max-line-length';
55
import minLength from './min-length';
66
import notEmpty from './not-empty';
77

8-
export * from './types';
98
export {ensureCase as case};
109
export {ensureEnum as enum};
1110
export {maxLength, maxLineLength, minLength, notEmpty};

@commitlint/ensure/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"outDir": "./lib"
77
},
88
"include": ["./src/**/*.ts"],
9-
"exclude": ["./src/**/*.test.ts", "./lib/**/*"]
9+
"exclude": ["./src/**/*.test.ts", "./lib/**/*"],
10+
"references": [{ "path": "../types" }]
1011
}

@commitlint/is-ignored/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@types/semver": "7.1.0"
4141
},
4242
"dependencies": {
43+
"@commitlint/types": "^8.3.4",
4344
"semver": "7.1.2"
4445
}
4546
}

@commitlint/is-ignored/src/defaults.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as semver from 'semver';
2-
import {Matcher} from './types';
2+
import {Matcher} from '@commitlint/types';
33

44
const isSemver = (c: string): boolean => {
55
const firstLine = c.split('\n').shift();

@commitlint/is-ignored/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * from './is-ignored';
2-
export * from './types';
2+
export {default} from './is-ignored';

@commitlint/is-ignored/src/is-ignored.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import {wildcards} from './defaults';
2-
import {Matcher} from './types';
3-
4-
export interface IsIgnoredOptions {
5-
ignores?: Matcher[];
6-
defaults?: boolean;
7-
}
2+
import {IsIgnoredOptions} from '@commitlint/types';
83

94
export default function isIgnored(
105
commit: string = '',

@commitlint/is-ignored/src/types.ts

-1
This file was deleted.

@commitlint/is-ignored/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"exclude": [
1212
"./src/**/*.test.ts",
1313
"./lib/**/*"
14-
]
14+
],
15+
"references": [{"path": "../types"}]
1516
}

@commitlint/lint/package.json

+4-10
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,14 @@
22
"name": "@commitlint/lint",
33
"version": "8.3.5",
44
"description": "Lint a string against commitlint rules",
5-
"main": "lib/index.js",
5+
"main": "lib/lint.js",
6+
"types": "lib/lint.d.ts",
67
"files": [
78
"lib/"
89
],
910
"scripts": {
10-
"build": "cross-env NODE_ENV=production babel src --out-dir lib --source-maps",
1111
"deps": "dep-check",
12-
"pkg": "pkg-check --skip-import",
13-
"start": "yarn run watch",
14-
"watch": "babel src --out-dir lib --watch --source-maps"
15-
},
16-
"babel": {
17-
"presets": [
18-
"babel-preset-commitlint"
19-
]
12+
"pkg": "pkg-check --skip-import"
2013
},
2114
"engines": {
2215
"node": ">=4"
@@ -53,6 +46,7 @@
5346
"@commitlint/is-ignored": "^8.3.5",
5447
"@commitlint/parse": "^8.3.4",
5548
"@commitlint/rules": "^8.3.4",
49+
"@commitlint/types": "^8.3.4",
5650
"lodash": "^4.17.15"
5751
}
5852
}
+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

+50-35
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
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 from '@commitlint/rules';
55
import toPairs from 'lodash/toPairs';
66
import values from 'lodash/values';
7+
import {buildCommitMesage} from './commit-message';
8+
import {
9+
LintRuleConfig,
10+
LintOptions,
11+
LintRuleOutcome,
12+
Rule,
13+
Plugin,
14+
RuleSeverity
15+
} from '@commitlint/types';
16+
17+
export default async function lint(
18+
message: string,
19+
rawRulesConfig?: LintRuleConfig,
20+
rawOpts?: LintOptions
21+
) {
22+
const opts = rawOpts
23+
? rawOpts
24+
: {defaultIgnores: undefined, ignores: undefined};
25+
const rulesConfig = rawRulesConfig || {};
726

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 = {}) => {
1827
// Found a wildcard match, skip
1928
if (
2029
isIgnored(message, {defaults: opts.defaultIgnores, ignores: opts.ignores})
@@ -29,33 +38,35 @@ export default async (message, rules = {}, opts = {}) => {
2938

3039
// Parse the commit message
3140
const parsed = await parse(message, undefined, opts.parserOpts);
41+
const allRules: Map<string, Rule<unknown> | Rule<never>> = new Map(
42+
Object.entries(defaultRules)
43+
);
3244

33-
const mergedImplementations = Object.assign({}, implementations);
3445
if (opts.plugins) {
35-
values(opts.plugins).forEach(plugin => {
46+
values(opts.plugins).forEach((plugin: Plugin) => {
3647
if (plugin.rules) {
37-
Object.keys(plugin.rules).forEach(ruleKey => {
38-
mergedImplementations[ruleKey] = plugin.rules[ruleKey];
39-
});
48+
Object.keys(plugin.rules).forEach(ruleKey =>
49+
allRules.set(ruleKey, plugin.rules[ruleKey])
50+
);
4051
}
4152
});
4253
}
4354

4455
// Find invalid rules configs
45-
const missing = Object.keys(rules).filter(
46-
name => typeof mergedImplementations[name] !== 'function'
56+
const missing = Object.keys(rulesConfig).filter(
57+
name => typeof allRules.get(name) !== 'function'
4758
);
4859

4960
if (missing.length > 0) {
50-
const names = Object.keys(mergedImplementations);
61+
const names = [...allRules.keys()];
5162
throw new RangeError(
5263
`Found invalid rule names: ${missing.join(
5364
', '
5465
)}. Supported rule names are: ${names.join(', ')}`
5566
);
5667
}
5768

58-
const invalid = toPairs(rules)
69+
const invalid = toPairs(rulesConfig)
5970
.map(([name, config]) => {
6071
if (!Array.isArray(config)) {
6172
return new Error(
@@ -65,7 +76,13 @@ export default async (message, rules = {}, opts = {}) => {
6576
);
6677
}
6778

68-
const [level, when] = config;
79+
const [level] = config;
80+
81+
if (level === RuleSeverity.Disabled && config.length === 1) {
82+
return null;
83+
}
84+
85+
const [, when] = config;
6986

7087
if (typeof level !== 'number' || isNaN(level)) {
7188
return new Error(
@@ -75,10 +92,6 @@ export default async (message, rules = {}, opts = {}) => {
7592
);
7693
}
7794

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

114127
return null;
115128
})
116-
.filter(item => item instanceof Error);
129+
.filter((item): item is Error => item instanceof Error);
117130

118131
if (invalid.length > 0) {
119132
throw new Error(invalid.map(i => i.message).join('\n'));
120133
}
121134

122135
// Validate against all rules
123-
const results = toPairs(rules)
124-
.filter(entry => {
125-
const [, [level]] = toPairs(entry);
126-
return level > 0;
127-
})
136+
const results = toPairs(rulesConfig)
137+
.filter(([, [level]]) => level > 0)
128138
.map(entry => {
129139
const [name, config] = entry;
130140
const [level, when, value] = config;
@@ -134,9 +144,14 @@ export default async (message, rules = {}, opts = {}) => {
134144
return null;
135145
}
136146

137-
const rule = mergedImplementations[name];
147+
const rule = allRules.get(name);
148+
149+
if (!rule) {
150+
throw new Error(`Could not find rule implementation for ${name}`);
151+
}
138152

139-
const [valid, message] = rule(parsed, when, value);
153+
const executableRule = rule as Rule<unknown>;
154+
const [valid, message] = executableRule(parsed, when, value);
140155

141156
return {
142157
level,
@@ -145,7 +160,7 @@ export default async (message, rules = {}, opts = {}) => {
145160
message
146161
};
147162
})
148-
.filter(Boolean);
163+
.filter((result): result is LintRuleOutcome => result !== null);
149164

150165
const errors = results.filter(result => result.level === 2 && !result.valid);
151166
const warnings = results.filter(
@@ -160,4 +175,4 @@ export default async (message, rules = {}, opts = {}) => {
160175
warnings,
161176
input: buildCommitMesage(parsed)
162177
};
163-
};
178+
}

@commitlint/lint/tsconfig.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"extends": "../../tsconfig.shared.json",
3+
"compilerOptions": {
4+
"composite": true,
5+
"rootDir": "./src",
6+
"outDir": "./lib"
7+
},
8+
"include": [
9+
"./src"
10+
],
11+
"exclude": [
12+
"./src/**/*.test.ts",
13+
"./lib/**/*"
14+
],
15+
"references": [
16+
{"path": "../is-ignored"},
17+
{"path": "../parse"},
18+
{"path": "../rules"},
19+
{"path": "../types"}
20+
]
21+
}

@commitlint/load/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"dependencies": {
4242
"@commitlint/execute-rule": "^8.3.4",
4343
"@commitlint/resolve-extends": "^8.3.5",
44+
"@commitlint/types": "^8.3.5",
4445
"chalk": "3.0.0",
4546
"cosmiconfig": "^6.0.0",
4647
"lodash": "^4.17.15",

0 commit comments

Comments
 (0)