-
Notifications
You must be signed in to change notification settings - Fork 40
/
spellcheck-commit.js
117 lines (101 loc) · 3.47 KB
/
spellcheck-commit.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* eslint-disable unicorn/no-abusive-eslint-disable */
/* eslint-disable */
'use strict';
const spellcheck = require('spellchecker');
const pkg = require('./package.json');
const read = require('fs').promises.readFile;
const execa = require('execa');
const debug = require('debug')(
`${require(`${process.cwd()}/package.json`).name}:spellcheck-commit`
);
const tryToRead = async (path) => {
try {
debug(`attempting to read ${path}`);
const data = await read(path);
debug(`successfully read ${path}`);
return data;
} catch (ignored) {
debug(`failed to read ${path}`);
return '';
}
};
const asJson = (str) => {
try {
const json = JSON.parse(str.toString('utf-8'));
debug('json parse successful!');
return [
...(json?.['cSpell.words'] || []),
...(json?.['cSpell.userWords'] || []),
...(json?.['cSpell.ignoreWords'] || [])
];
} catch (ignored) {
debug('json parse failed!');
return [];
}
};
const asText = (str) => str.toString('utf-8').split('\n');
const isPascalCase = (w) => /^([A-Z]{2,}.+|[A-Z][a-z]+[A-Z].*)$/.test(w);
const isCamelCase = (w) => /^[a-z]+[A-Z]+.*$/.test(w);
const isAllCaps = (w) => /^[^a-z]+$/.test(w);
const splitOutWords = (phrase) =>
[...phrase.split(/[^a-zA-Z]+/g), phrase].filter(Boolean);
const keys = (obj) => Object.keys(obj).map(splitOutWords);
(async () => {
const lastCommitMsg = (await read('./.git/COMMIT_EDITMSG')).toString('utf-8');
const homeDir = require('os').homedir();
debug(`lastCommitMsg: ${lastCommitMsg}`);
debug(`homeDir: ${homeDir}`);
const ignoreWords = Array.from(
new Set(
[
...(await Promise.all([
tryToRead('./.spellcheckignore').then(asText),
tryToRead(`${homeDir}/.config/_spellcheckignore`).then(asText),
tryToRead('./.vscode/settings.json').then(asJson),
tryToRead(`${homeDir}/.config/Code/User/settings.json`).then(asJson)
])),
...require('text-extensions'),
// ? Popular contractions
...['ve', 're', 's', 'll', 't', 'd', 'o', 'ol'],
...keys(pkg.dependencies || {}),
...keys(pkg.devDependencies || {}),
...keys(pkg.scripts || {}),
...splitOutWords(
(await execa('git', ['log', '--format="%B"', 'HEAD~1'])).stdout
).slice(0, -1)
]
.flat()
.filter(Boolean)
.map((word) => splitOutWords(word.trim().toLowerCase()))
.flat()
)
);
const typos = Array.from(
new Set(
spellcheck
.checkSpelling(lastCommitMsg)
.map((typoLocation) =>
lastCommitMsg.slice(typoLocation.start, typoLocation.end).trim().split("'")
)
.flat()
.filter((w) => !isAllCaps(w) && !isCamelCase(w) && !isPascalCase(w))
.map((w) => w.toLowerCase())
.filter((typo) => !ignoreWords.includes(typo))
)
);
debug('typos: %O', typos);
if (typos.length) {
console.warn('WARNING: there may be misspelled words in your commit message!');
console.warn('Commit messages can be fixed before push with `git commit -S --amend`');
console.warn('---');
for (const typo of typos.slice(0, 5)) {
const corrections = spellcheck.getCorrectionsForMisspelling(typo);
const suggestion = corrections.length
? ` (did you mean ${corrections.slice(0, 5).join(', ')}?)`
: '';
console.warn(`${typo}${suggestion}`);
}
typos.length > 5 && console.warn(`${typos.length - 5} more...`);
typos.length && console.warn('---');
}
})();