1 | import util from 'util';
|
2 | import isIgnored from '@commitlint/is-ignored';
|
3 | import parse from '@commitlint/parse';
|
4 | import defaultRules from '@commitlint/rules';
|
5 | import { RuleConfigSeverity } from '@commitlint/types';
|
6 | import { buildCommitMessage } from './commit-message.js';
|
7 | export default async function lint(message, rawRulesConfig, rawOpts) {
|
8 | const opts = rawOpts
|
9 | ? rawOpts
|
10 | : { defaultIgnores: undefined, ignores: undefined };
|
11 | const rulesConfig = rawRulesConfig || {};
|
12 |
|
13 | if (isIgnored(message, { defaults: opts.defaultIgnores, ignores: opts.ignores })) {
|
14 | return {
|
15 | valid: true,
|
16 | errors: [],
|
17 | warnings: [],
|
18 | input: message,
|
19 | };
|
20 | }
|
21 |
|
22 | const parsed = message === ''
|
23 | ? { header: null, body: null, footer: null }
|
24 | : await parse(message, undefined, opts.parserOpts);
|
25 | if (parsed.header === null &&
|
26 | parsed.body === null &&
|
27 | parsed.footer === null) {
|
28 |
|
29 | return {
|
30 | valid: true,
|
31 | errors: [],
|
32 | warnings: [],
|
33 | input: message,
|
34 | };
|
35 | }
|
36 | const allRules = new Map(Object.entries(defaultRules));
|
37 | if (opts.plugins) {
|
38 | Object.values(opts.plugins).forEach((plugin) => {
|
39 | if (plugin.rules) {
|
40 | Object.keys(plugin.rules).forEach((ruleKey) => allRules.set(ruleKey, plugin.rules[ruleKey]));
|
41 | }
|
42 | });
|
43 | }
|
44 |
|
45 | const missing = Object.keys(rulesConfig).filter((name) => typeof allRules.get(name) !== 'function');
|
46 | if (missing.length > 0) {
|
47 | const names = [...allRules.keys()];
|
48 | throw new RangeError(`Found invalid rule names: ${missing.join(', ')}. Supported rule names are: ${names.join(', ')}`);
|
49 | }
|
50 | const invalid = Object.entries(rulesConfig)
|
51 | .map(([name, config]) => {
|
52 | if (!Array.isArray(config)) {
|
53 | return new Error(`config for rule ${name} must be array, received ${util.inspect(config)} of type ${typeof config}`);
|
54 | }
|
55 | const [level] = config;
|
56 | if (level === RuleConfigSeverity.Disabled && config.length === 1) {
|
57 | return null;
|
58 | }
|
59 | const [, when] = config;
|
60 | if (typeof level !== 'number' || isNaN(level)) {
|
61 | return new Error(`level for rule ${name} must be number, received ${util.inspect(level)} of type ${typeof level}`);
|
62 | }
|
63 | if (config.length < 2 || config.length > 3) {
|
64 | return new Error(`config for rule ${name} must be 2 or 3 items long, received ${util.inspect(config)} of length ${config.length}`);
|
65 | }
|
66 | if (level < 0 || level > 2) {
|
67 | return new RangeError(`level for rule ${name} must be between 0 and 2, received ${util.inspect(level)}`);
|
68 | }
|
69 | if (typeof when !== 'string') {
|
70 | return new Error(`condition for rule ${name} must be string, received ${util.inspect(when)} of type ${typeof when}`);
|
71 | }
|
72 | if (when !== 'never' && when !== 'always') {
|
73 | return new Error(`condition for rule ${name} must be "always" or "never", received ${util.inspect(when)}`);
|
74 | }
|
75 | return null;
|
76 | })
|
77 | .filter((item) => item instanceof Error);
|
78 | if (invalid.length > 0) {
|
79 | throw new Error(invalid.map((i) => i.message).join('\n'));
|
80 | }
|
81 |
|
82 | const pendingResults = Object.entries(rulesConfig)
|
83 |
|
84 | .filter(([, config]) => !!config && config.length && config[0] > 0)
|
85 | .map(async (entry) => {
|
86 | const [name, config] = entry;
|
87 | const [level, when, value] = config;
|
88 | const rule = allRules.get(name);
|
89 | if (!rule) {
|
90 | throw new Error(`Could not find rule implementation for ${name}`);
|
91 | }
|
92 | const executableRule = rule;
|
93 | const [valid, message] = await executableRule(parsed, when, value);
|
94 | return {
|
95 | level,
|
96 | valid,
|
97 | name,
|
98 | message,
|
99 | };
|
100 | });
|
101 | const results = (await Promise.all(pendingResults)).filter((result) => result !== null);
|
102 | const errors = results.filter((result) => result.level === 2 && !result.valid);
|
103 | const warnings = results.filter((result) => result.level === 1 && !result.valid);
|
104 | const valid = errors.length === 0;
|
105 | return {
|
106 | valid,
|
107 | errors,
|
108 | warnings,
|
109 | input: buildCommitMessage(parsed),
|
110 | };
|
111 | }
|
112 |
|
\ | No newline at end of file |