@mark1russell7/ste-lint 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-core.d.ts +92 -0
- package/dist/config-core.d.ts.map +1 -0
- package/dist/config-core.js +198 -0
- package/dist/config-core.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +16 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +16 -0
- package/dist/core.js.map +1 -0
- package/dist/dictionary-core.d.ts +72 -0
- package/dist/dictionary-core.d.ts.map +1 -0
- package/dist/dictionary-core.js +150 -0
- package/dist/dictionary-core.js.map +1 -0
- package/dist/dictionary.d.ts +9 -0
- package/dist/dictionary.d.ts.map +1 -0
- package/dist/dictionary.js +26 -0
- package/dist/dictionary.js.map +1 -0
- package/dist/engine.d.ts +44 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +122 -0
- package/dist/engine.js.map +1 -0
- package/dist/files.d.ts +35 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +181 -0
- package/dist/files.js.map +1 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +61 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lexicon.d.ts +33 -0
- package/dist/lexicon.d.ts.map +1 -0
- package/dist/lexicon.js +71 -0
- package/dist/lexicon.js.map +1 -0
- package/dist/lint.d.ts +18 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +23 -0
- package/dist/lint.js.map +1 -0
- package/dist/rules/about-quantity.d.ts +3 -0
- package/dist/rules/about-quantity.d.ts.map +1 -0
- package/dist/rules/about-quantity.js +34 -0
- package/dist/rules/about-quantity.js.map +1 -0
- package/dist/rules/index.d.ts +8 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +35 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/ing-verb.d.ts +9 -0
- package/dist/rules/ing-verb.d.ts.map +1 -0
- package/dist/rules/ing-verb.js +60 -0
- package/dist/rules/ing-verb.js.map +1 -0
- package/dist/rules/latin-abbreviation.d.ts +3 -0
- package/dist/rules/latin-abbreviation.d.ts.map +1 -0
- package/dist/rules/latin-abbreviation.js +48 -0
- package/dist/rules/latin-abbreviation.js.map +1 -0
- package/dist/rules/missing-subject.d.ts +16 -0
- package/dist/rules/missing-subject.d.ts.map +1 -0
- package/dist/rules/missing-subject.js +138 -0
- package/dist/rules/missing-subject.js.map +1 -0
- package/dist/rules/modal-verb.d.ts +3 -0
- package/dist/rules/modal-verb.d.ts.map +1 -0
- package/dist/rules/modal-verb.js +72 -0
- package/dist/rules/modal-verb.js.map +1 -0
- package/dist/rules/noun-cluster.d.ts +17 -0
- package/dist/rules/noun-cluster.d.ts.map +1 -0
- package/dist/rules/noun-cluster.js +83 -0
- package/dist/rules/noun-cluster.js.map +1 -0
- package/dist/rules/paragraph-length.d.ts +3 -0
- package/dist/rules/paragraph-length.d.ts.map +1 -0
- package/dist/rules/paragraph-length.js +19 -0
- package/dist/rules/paragraph-length.js.map +1 -0
- package/dist/rules/passive-instruction.d.ts +15 -0
- package/dist/rules/passive-instruction.d.ts.map +1 -0
- package/dist/rules/passive-instruction.js +38 -0
- package/dist/rules/passive-instruction.js.map +1 -0
- package/dist/rules/pronouns.d.ts +4 -0
- package/dist/rules/pronouns.d.ts.map +1 -0
- package/dist/rules/pronouns.js +57 -0
- package/dist/rules/pronouns.js.map +1 -0
- package/dist/rules/rule.d.ts +46 -0
- package/dist/rules/rule.d.ts.map +1 -0
- package/dist/rules/rule.js +31 -0
- package/dist/rules/rule.js.map +1 -0
- package/dist/rules/semicolon.d.ts +3 -0
- package/dist/rules/semicolon.d.ts.map +1 -0
- package/dist/rules/semicolon.js +20 -0
- package/dist/rules/semicolon.js.map +1 -0
- package/dist/rules/sentence-length.d.ts +3 -0
- package/dist/rules/sentence-length.d.ts.map +1 -0
- package/dist/rules/sentence-length.js +31 -0
- package/dist/rules/sentence-length.js.map +1 -0
- package/dist/rules/unknown-word.d.ts +9 -0
- package/dist/rules/unknown-word.d.ts.map +1 -0
- package/dist/rules/unknown-word.js +49 -0
- package/dist/rules/unknown-word.js.map +1 -0
- package/dist/rules/word-list-matcher.d.ts +25 -0
- package/dist/rules/word-list-matcher.d.ts.map +1 -0
- package/dist/rules/word-list-matcher.js +60 -0
- package/dist/rules/word-list-matcher.js.map +1 -0
- package/dist/rules/word-list.d.ts +3 -0
- package/dist/rules/word-list.d.ts.map +1 -0
- package/dist/rules/word-list.js +26 -0
- package/dist/rules/word-list.js.map +1 -0
- package/dist/run-cli.d.ts +27 -0
- package/dist/run-cli.d.ts.map +1 -0
- package/dist/run-cli.js +176 -0
- package/dist/run-cli.js.map +1 -0
- package/dist/source/blocks.d.ts +55 -0
- package/dist/source/blocks.d.ts.map +1 -0
- package/dist/source/blocks.js +464 -0
- package/dist/source/blocks.js.map +1 -0
- package/dist/source/code.d.ts +9 -0
- package/dist/source/code.d.ts.map +1 -0
- package/dist/source/code.js +16 -0
- package/dist/source/code.js.map +1 -0
- package/dist/source/inline.d.ts +42 -0
- package/dist/source/inline.d.ts.map +1 -0
- package/dist/source/inline.js +396 -0
- package/dist/source/inline.js.map +1 -0
- package/dist/source/jsx.d.ts +18 -0
- package/dist/source/jsx.d.ts.map +1 -0
- package/dist/source/jsx.js +137 -0
- package/dist/source/jsx.js.map +1 -0
- package/dist/source/lines.d.ts +34 -0
- package/dist/source/lines.d.ts.map +1 -0
- package/dist/source/lines.js +83 -0
- package/dist/source/lines.js.map +1 -0
- package/dist/source/markdown.d.ts +23 -0
- package/dist/source/markdown.d.ts.map +1 -0
- package/dist/source/markdown.js +42 -0
- package/dist/source/markdown.js.map +1 -0
- package/dist/source/suppressions.d.ts +38 -0
- package/dist/source/suppressions.d.ts.map +1 -0
- package/dist/source/suppressions.js +69 -0
- package/dist/source/suppressions.js.map +1 -0
- package/dist/source/tsdoc.d.ts +40 -0
- package/dist/source/tsdoc.d.ts.map +1 -0
- package/dist/source/tsdoc.js +176 -0
- package/dist/source/tsdoc.js.map +1 -0
- package/dist/text/grammar.d.ts +91 -0
- package/dist/text/grammar.d.ts.map +1 -0
- package/dist/text/grammar.js +289 -0
- package/dist/text/grammar.js.map +1 -0
- package/dist/text/phrases.d.ts +30 -0
- package/dist/text/phrases.d.ts.map +1 -0
- package/dist/text/phrases.js +65 -0
- package/dist/text/phrases.js.map +1 -0
- package/dist/text/sentences.d.ts +45 -0
- package/dist/text/sentences.d.ts.map +1 -0
- package/dist/text/sentences.js +141 -0
- package/dist/text/sentences.js.map +1 -0
- package/dist/text/tokens.d.ts +35 -0
- package/dist/text/tokens.d.ts.map +1 -0
- package/dist/text/tokens.js +113 -0
- package/dist/text/tokens.js.map +1 -0
- package/dist/text/words.d.ts +58 -0
- package/dist/text/words.d.ts.map +1 -0
- package/dist/text/words.js +159 -0
- package/dist/text/words.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/units.d.ts +47 -0
- package/dist/units.d.ts.map +1 -0
- package/dist/units.js +42 -0
- package/dist/units.js.map +1 -0
- package/dist/word-list.d.ts +34 -0
- package/dist/word-list.d.ts.map +1 -0
- package/dist/word-list.js +90 -0
- package/dist/word-list.js.map +1 -0
- package/package.json +54 -3
- package/src/cli.ts +9 -0
- package/src/config-core.ts +258 -0
- package/src/config.test.ts +188 -0
- package/src/config.ts +83 -0
- package/src/core.test.ts +87 -0
- package/src/core.ts +32 -0
- package/src/dictionary-core.ts +158 -0
- package/src/dictionary.test.ts +82 -0
- package/src/dictionary.ts +26 -0
- package/src/engine.ts +138 -0
- package/src/files.test.ts +107 -0
- package/src/files.ts +191 -0
- package/src/format.ts +78 -0
- package/src/index.test.ts +28 -0
- package/src/index.ts +38 -0
- package/src/lexicon.ts +92 -0
- package/src/lint.test.ts +182 -0
- package/src/lint.ts +30 -0
- package/src/rules/about-quantity.ts +33 -0
- package/src/rules/index.ts +40 -0
- package/src/rules/ing-verb.test.ts +48 -0
- package/src/rules/ing-verb.ts +54 -0
- package/src/rules/latin-abbreviation.ts +46 -0
- package/src/rules/missing-subject.test.ts +53 -0
- package/src/rules/missing-subject.ts +129 -0
- package/src/rules/modal-verb.test.ts +45 -0
- package/src/rules/modal-verb.ts +72 -0
- package/src/rules/noun-cluster.test.ts +49 -0
- package/src/rules/noun-cluster.ts +82 -0
- package/src/rules/paragraph-length.test.ts +30 -0
- package/src/rules/paragraph-length.ts +19 -0
- package/src/rules/passive-instruction.test.ts +38 -0
- package/src/rules/passive-instruction.ts +37 -0
- package/src/rules/pronouns.test.ts +38 -0
- package/src/rules/pronouns.ts +54 -0
- package/src/rules/punctuation.test.ts +59 -0
- package/src/rules/rule.ts +73 -0
- package/src/rules/semicolon.ts +19 -0
- package/src/rules/sentence-length.test.ts +52 -0
- package/src/rules/sentence-length.ts +32 -0
- package/src/rules/unknown-word.test.ts +81 -0
- package/src/rules/unknown-word.ts +43 -0
- package/src/rules/word-list-matcher.ts +68 -0
- package/src/rules/word-list.test.ts +110 -0
- package/src/rules/word-list.ts +25 -0
- package/src/run-cli.test.ts +189 -0
- package/src/run-cli.ts +193 -0
- package/src/source/blocks.ts +534 -0
- package/src/source/code.ts +17 -0
- package/src/source/inline.test.ts +92 -0
- package/src/source/inline.ts +416 -0
- package/src/source/jsx.test.ts +46 -0
- package/src/source/jsx.ts +147 -0
- package/src/source/lines.test.ts +51 -0
- package/src/source/lines.ts +91 -0
- package/src/source/markdown.test.ts +210 -0
- package/src/source/markdown.ts +49 -0
- package/src/source/suppressions.ts +79 -0
- package/src/source/tsdoc.test.ts +137 -0
- package/src/source/tsdoc.ts +189 -0
- package/src/text/grammar.ts +304 -0
- package/src/text/phrases.ts +72 -0
- package/src/text/sentences.ts +148 -0
- package/src/text/text.test.ts +262 -0
- package/src/text/tokens.ts +135 -0
- package/src/text/words.ts +183 -0
- package/src/types.ts +39 -0
- package/src/units.ts +86 -0
- package/src/word-list.ts +128 -0
- package/ste.config.schema.json +87 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project word list: plain-language substitutions for the documentation
|
|
3
|
+
* of this repository. This list is the style guide of the project, written
|
|
4
|
+
* for it. It is not a copy of the ASD-STE100 dictionary. The local
|
|
5
|
+
* dictionary file (refer to `dictionary.ts`) does that job, and it is not
|
|
6
|
+
* part of the repository at any time.
|
|
7
|
+
*
|
|
8
|
+
* Each entry has the words or phrases to find, the words to write instead,
|
|
9
|
+
* and a severity. `ste.config.json` can add entries, change them, or set an
|
|
10
|
+
* entry to "off".
|
|
11
|
+
*/
|
|
12
|
+
function entry(input) {
|
|
13
|
+
return {
|
|
14
|
+
id: input.id ?? input.match[0],
|
|
15
|
+
match: input.match,
|
|
16
|
+
suggest: input.suggest ?? null,
|
|
17
|
+
note: input.note ?? null,
|
|
18
|
+
severity: input.severity ?? "error",
|
|
19
|
+
verbOnly: input.verbOnly ?? false,
|
|
20
|
+
context: input.context ?? null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** The default word list. */
|
|
24
|
+
export const DEFAULT_WORD_LIST = [
|
|
25
|
+
// Long or formal words that have a short, common equivalent.
|
|
26
|
+
entry({ match: ["utilize", "utilizes", "utilized", "utilizing", "utilization", "utilise", "utilises", "utilised", "utilising"], suggest: "\"use\"" }),
|
|
27
|
+
entry({ match: ["initiate", "initiates", "initiated", "initiating"], suggest: "\"start\"" }),
|
|
28
|
+
entry({ match: ["ensure", "ensures", "ensured", "ensuring"], suggest: "\"make sure\"", note: "Write \"that\" after it: \"make sure that ...\"." }),
|
|
29
|
+
entry({ match: ["via"], suggest: "\"through\" or \"with\"" }),
|
|
30
|
+
entry({ match: ["leverage", "leverages", "leveraged", "leveraging"], suggest: "\"use\"" }),
|
|
31
|
+
entry({ match: ["facilitate", "facilitates", "facilitated", "facilitating"], suggest: "\"help\" or \"make ... easier\"" }),
|
|
32
|
+
entry({ match: ["commence", "commences", "commenced", "commencing"], suggest: "\"start\"" }),
|
|
33
|
+
entry({ match: ["terminate", "terminates", "terminated", "terminating"], suggest: "\"stop\"" }),
|
|
34
|
+
entry({ match: ["obtain", "obtains", "obtained", "obtaining"], suggest: "\"get\"" }),
|
|
35
|
+
entry({ match: ["assist", "assists", "assisted", "assisting"], suggest: "\"help\"" }),
|
|
36
|
+
entry({ match: ["modify", "modifies", "modified", "modifying"], suggest: "\"change\"" }),
|
|
37
|
+
entry({ match: ["indicate", "indicates", "indicated", "indicating"], suggest: "\"show\"" }),
|
|
38
|
+
entry({ match: ["demonstrate", "demonstrates", "demonstrated", "demonstrating"], suggest: "\"show\"" }),
|
|
39
|
+
entry({ match: ["perform", "performs", "performed", "performing"], suggest: "\"do\"" }),
|
|
40
|
+
entry({ match: ["attempt", "attempts", "attempted", "attempting"], suggest: "\"try\"" }),
|
|
41
|
+
entry({ match: ["numerous"], suggest: "\"many\", or give the number" }),
|
|
42
|
+
entry({ match: ["additional"], suggest: "\"more\"" }),
|
|
43
|
+
entry({ match: ["additionally"], suggest: "\"also\"" }),
|
|
44
|
+
entry({ match: ["regarding"], suggest: "\"about\"" }),
|
|
45
|
+
entry({ match: ["hence"], suggest: "\"thus\"" }),
|
|
46
|
+
// Phrases that have a shorter equivalent.
|
|
47
|
+
entry({ match: ["prior to"], suggest: "\"before\"" }),
|
|
48
|
+
entry({ match: ["in order to"], suggest: "\"to\"" }),
|
|
49
|
+
entry({ match: ["due to the fact that"], suggest: "\"because\"" }),
|
|
50
|
+
entry({ match: ["due to"], suggest: "\"because of\"" }),
|
|
51
|
+
entry({ match: ["in the event that"], suggest: "\"if\"" }),
|
|
52
|
+
entry({ match: ["at this point in time"], suggest: "\"at this time\"" }),
|
|
53
|
+
entry({ match: ["in an effort to"], suggest: "\"to\"" }),
|
|
54
|
+
entry({ match: ["for the purpose of"], suggest: "\"to\" or \"for\"" }),
|
|
55
|
+
entry({ match: ["with respect to", "with regard to"], suggest: "\"about\" or \"for\"" }),
|
|
56
|
+
entry({ match: ["as well as"], suggest: "\"and\"" }),
|
|
57
|
+
entry({ match: ["a number of"], suggest: "\"some\", or give the number" }),
|
|
58
|
+
entry({ match: ["is able to", "are able to", "be able to"], suggest: "\"can\"" }),
|
|
59
|
+
entry({ match: ["such as"], suggest: "\"for example\"" }),
|
|
60
|
+
entry({ match: ["follow these steps", "follow the steps"], suggest: "\"do these steps\" or \"do the steps\"" }),
|
|
61
|
+
// Connecting words.
|
|
62
|
+
entry({ match: ["however"], suggest: "\"but\"", note: "Start a new sentence with \"But\" if necessary." }),
|
|
63
|
+
entry({ match: ["therefore"], suggest: "\"thus\" or \"as a result\"" }),
|
|
64
|
+
entry({ match: ["whether or not", "whether"], suggest: "\"if\"" }),
|
|
65
|
+
// Verbs that have a simpler equivalent.
|
|
66
|
+
entry({ match: ["allow", "allows", "allowed", "allowing"], suggest: "\"let\" or \"lets\"", note: "For an adjective, write \"permitted\"." }),
|
|
67
|
+
entry({ match: ["require", "requires", "required", "requiring"], suggest: "\"necessary\"", note: "For example, write \"X is necessary for Y\", or use the imperative." }),
|
|
68
|
+
entry({ match: ["provided that"], suggest: "\"if\"" }),
|
|
69
|
+
entry({ match: ["provide", "provides", "provided", "providing"], suggest: "\"give\" or \"gives\"" }),
|
|
70
|
+
entry({ match: ["execute", "executes", "executed", "executing"], suggest: "\"do\", \"does\" or \"start\"" }),
|
|
71
|
+
entry({ match: ["create", "creates", "created", "creating"], suggest: "\"make\" or \"makes\"" }),
|
|
72
|
+
entry({ match: ["kill", "kills", "killed", "killing"], suggest: "\"stop\"" }),
|
|
73
|
+
entry({ id: "displays", match: ["displayed", "displaying"], suggest: "\"show\" or \"shows\"", severity: "warning" }),
|
|
74
|
+
entry({ id: "display", match: ["display", "displays"], suggest: "\"show\" or \"shows\"", severity: "warning", verbOnly: true }),
|
|
75
|
+
entry({ id: "returns", match: ["returns", "returned", "returning"], suggest: "\"gives\"", severity: "warning", note: "For a function, write \"This function gives ...\"." }),
|
|
76
|
+
entry({ id: "return", match: ["return"], suggest: "\"give\" or \"gives\"", severity: "warning", verbOnly: true }),
|
|
77
|
+
entry({ id: "call", match: ["call", "calls", "called", "calling"], suggest: "\"use\" or \"start\"", severity: "warning", verbOnly: true, note: "This applies when the verb means to start a function." }),
|
|
78
|
+
entry({ id: "ran", match: ["ran"], suggest: "\"started\" or \"operated\"", severity: "warning" }),
|
|
79
|
+
entry({ id: "run", match: ["run", "runs", "running"], suggest: "\"start\", \"starts\" or \"operates\"", severity: "warning", verbOnly: true }),
|
|
80
|
+
entry({ id: "see", match: ["see"], suggest: "\"refer to\"", severity: "warning", context: "reference" }),
|
|
81
|
+
// Words that do not add information.
|
|
82
|
+
entry({ match: ["simple", "simply", "easily"], severity: "warning", note: "The word tells the reader what to think. Tell the reader what to do." }),
|
|
83
|
+
entry({ match: ["just"], suggest: "\"only\"", severity: "warning", note: "Or remove the word." }),
|
|
84
|
+
entry({ match: ["basically", "obviously", "of course"], severity: "warning", note: "The word does not add information." }),
|
|
85
|
+
entry({ match: ["please"], severity: "warning", note: "Write the instruction in the imperative." }),
|
|
86
|
+
// Time and negation.
|
|
87
|
+
entry({ match: ["currently", "now"], suggest: "\"at this time\"", note: "Or remove the word if the time is clear." }),
|
|
88
|
+
entry({ match: ["never"], suggest: "\"do not\"", note: "For a description, write \"not ... at any time\"." }),
|
|
89
|
+
];
|
|
90
|
+
//# sourceMappingURL=word-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"word-list.js","sourceRoot":"","sources":["../src/word-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkCH,SAAS,KAAK,CAAC,KAAkB;IAC7B,OAAO;QACH,EAAE,EAAG,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE;QAChC,KAAK,EAAG,KAAK,CAAC,KAAK;QACnB,OAAO,EAAG,KAAK,CAAC,OAAO,IAAI,IAAI;QAC/B,IAAI,EAAG,KAAK,CAAC,IAAI,IAAI,IAAI;QACzB,QAAQ,EAAG,KAAK,CAAC,QAAQ,IAAI,OAAO;QACpC,QAAQ,EAAG,KAAK,CAAC,QAAQ,IAAI,KAAK;QAClC,OAAO,EAAG,KAAK,CAAC,OAAO,IAAI,IAAI;KAClC,CAAC;AACN,CAAC;AAED,6BAA6B;AAC7B,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IACxD,6DAA6D;IAC7D,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IACvJ,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,WAAW,EAAE,CAAC;IAC9F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,OAAO,EAAG,eAAe,EAAE,IAAI,EAAG,kDAAkD,EAAE,CAAC;IACrJ,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAG,yBAAyB,EAAE,CAAC;IAC/D,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IAC5F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,CAAC,EAAE,OAAO,EAAG,iCAAiC,EAAE,CAAC;IAC5H,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,WAAW,EAAE,CAAC;IAC9F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IACjG,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IACtF,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IACvF,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,YAAY,EAAE,CAAC;IAC1F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IAC7F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IACzG,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IACzF,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IAC1F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAG,8BAA8B,EAAE,CAAC;IACzE,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,YAAY,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IACvD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,cAAc,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IACzD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAG,WAAW,EAAE,CAAC;IACvD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,OAAO,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IAElD,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAG,YAAY,EAAE,CAAC;IACvD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,aAAa,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IACtD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,sBAAsB,CAAC,EAAE,OAAO,EAAG,aAAa,EAAE,CAAC;IACpE,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAG,gBAAgB,EAAE,CAAC;IACzD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,mBAAmB,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IAC5D,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAG,kBAAkB,EAAE,CAAC;IAC1E,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IAC1D,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAG,mBAAmB,EAAE,CAAC;IACxE,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,EAAE,OAAO,EAAG,sBAAsB,EAAE,CAAC;IAC1F,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,YAAY,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IACtD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,aAAa,CAAC,EAAE,OAAO,EAAG,8BAA8B,EAAE,CAAC;IAC5E,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,CAAC;IACnF,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,CAAC,EAAE,OAAO,EAAG,iBAAiB,EAAE,CAAC;IAC3D,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,EAAE,OAAO,EAAG,wCAAwC,EAAE,CAAC;IAEjH,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,CAAC,EAAE,OAAO,EAAG,SAAS,EAAE,IAAI,EAAG,iDAAiD,EAAE,CAAC;IAC7G,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,WAAW,CAAC,EAAE,OAAO,EAAG,6BAA6B,EAAE,CAAC;IACzE,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IAEpE,wCAAwC;IACxC,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,OAAO,EAAG,qBAAqB,EAAE,IAAI,EAAG,wCAAwC,EAAE,CAAC;IAC/I,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,eAAe,EAAE,IAAI,EAAG,qEAAqE,EAAE,CAAC;IAC5K,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,eAAe,CAAC,EAAE,OAAO,EAAG,QAAQ,EAAE,CAAC;IACxD,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,uBAAuB,EAAE,CAAC;IACtG,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,+BAA+B,EAAE,CAAC;IAC9G,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,OAAO,EAAG,uBAAuB,EAAE,CAAC;IAClG,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,CAAC;IAC/E,KAAK,CAAC,EAAE,EAAE,EAAG,UAAU,EAAE,KAAK,EAAG,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,OAAO,EAAG,uBAAuB,EAAE,QAAQ,EAAG,SAAS,EAAE,CAAC;IACxH,KAAK,CAAC,EAAE,EAAE,EAAG,SAAS,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,OAAO,EAAG,uBAAuB,EAAE,QAAQ,EAAG,SAAS,EAAE,QAAQ,EAAG,IAAI,EAAE,CAAC;IACpI,KAAK,CAAC,EAAE,EAAE,EAAG,SAAS,EAAE,KAAK,EAAG,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,OAAO,EAAG,WAAW,EAAE,QAAQ,EAAG,SAAS,EAAE,IAAI,EAAG,oDAAoD,EAAE,CAAC;IACjL,KAAK,CAAC,EAAE,EAAE,EAAG,QAAQ,EAAE,KAAK,EAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAG,uBAAuB,EAAE,QAAQ,EAAG,SAAS,EAAE,QAAQ,EAAG,IAAI,EAAE,CAAC;IACtH,KAAK,CAAC,EAAE,EAAE,EAAG,MAAM,EAAE,KAAK,EAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,OAAO,EAAG,sBAAsB,EAAE,QAAQ,EAAG,SAAS,EAAE,QAAQ,EAAG,IAAI,EAAE,IAAI,EAAG,uDAAuD,EAAE,CAAC;IAC/M,KAAK,CAAC,EAAE,EAAE,EAAG,KAAK,EAAE,KAAK,EAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAG,6BAA6B,EAAE,QAAQ,EAAG,SAAS,EAAE,CAAC;IACrG,KAAK,CAAC,EAAE,EAAE,EAAG,KAAK,EAAE,KAAK,EAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,EAAG,uCAAuC,EAAE,QAAQ,EAAG,SAAS,EAAE,QAAQ,EAAG,IAAI,EAAE,CAAC;IACnJ,KAAK,CAAC,EAAE,EAAE,EAAG,KAAK,EAAE,KAAK,EAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAG,cAAc,EAAE,QAAQ,EAAG,SAAS,EAAE,OAAO,EAAG,WAAW,EAAE,CAAC;IAE7G,qCAAqC;IACrC,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAG,SAAS,EAAE,IAAI,EAAG,sEAAsE,EAAE,CAAC;IACtJ,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,MAAM,CAAC,EAAE,OAAO,EAAG,UAAU,EAAE,QAAQ,EAAG,SAAS,EAAE,IAAI,EAAG,qBAAqB,EAAE,CAAC;IACrG,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAG,SAAS,EAAE,IAAI,EAAG,oCAAoC,EAAE,CAAC;IAC7H,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAG,SAAS,EAAE,IAAI,EAAG,0CAA0C,EAAE,CAAC;IAEtG,qBAAqB;IACrB,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,OAAO,EAAG,kBAAkB,EAAE,IAAI,EAAG,0CAA0C,EAAE,CAAC;IACxH,KAAK,CAAC,EAAE,KAAK,EAAG,CAAC,OAAO,CAAC,EAAE,OAAO,EAAG,YAAY,EAAE,IAAI,EAAG,mDAAmD,EAAE,CAAC;CACnH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@mark1russell7/ste-lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "0.1.2",
|
|
5
|
+
"description": "A linter for the writing rules of ASD-STE100 Simplified Technical English in Markdown, MDX and TypeScript doc comments",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./core": {
|
|
18
|
+
"types": "./dist/core.d.ts",
|
|
19
|
+
"import": "./dist/core.js"
|
|
20
|
+
},
|
|
21
|
+
"./code": {
|
|
22
|
+
"types": "./dist/source/code.d.ts",
|
|
23
|
+
"import": "./dist/source/code.js"
|
|
24
|
+
},
|
|
25
|
+
"./ste.config.schema.json": "./ste.config.schema.json"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"ste-lint": "./dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"ste.config.schema.json"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/mark1russell7/ste-lint.git"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"typescript": "^6.0.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@mark1russell7/cue": "^1.0.2",
|
|
47
|
+
"@types/node": "^26.6.4",
|
|
48
|
+
"tsx": "^4.23.15",
|
|
49
|
+
"vitest": "^5.0.3"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc -b",
|
|
53
|
+
"typecheck": "tsc -p tsconfig.test.json && pnpm --filter @ste-lint/site run typecheck",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"lint:ste": "tsx src/cli.ts"
|
|
56
|
+
}
|
|
6
57
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of the configuration that need no file system: the types of
|
|
3
|
+
* `ste.config.json`, the defaults, the validation and the word list merge.
|
|
4
|
+
* A browser can use this module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isRuleId, RULE_IDS, type RuleId, type RuleSetting, type Severity } from "./types.js";
|
|
8
|
+
import { DEFAULT_WORD_LIST, type WordListEntry } from "./word-list.js";
|
|
9
|
+
|
|
10
|
+
export const CONFIG_FILE_NAME = "ste.config.json";
|
|
11
|
+
|
|
12
|
+
/** The environment variable that gives the path of the local dictionary. It has priority over `dictionaryPath`. */
|
|
13
|
+
export const DICTIONARY_ENV = "STE_DICTIONARY";
|
|
14
|
+
|
|
15
|
+
/** An entry of `wordList` in the configuration file. */
|
|
16
|
+
export type WordListEntryConfig = {
|
|
17
|
+
/** The ID of a default entry to change. A new entry gets the first word of `match` as its ID. */
|
|
18
|
+
readonly id? : string;
|
|
19
|
+
readonly match? : string | readonly string[];
|
|
20
|
+
readonly suggest? : string | null;
|
|
21
|
+
readonly note? : string | null;
|
|
22
|
+
readonly severity? : RuleSetting;
|
|
23
|
+
readonly verbOnly? : boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type LimitsConfig = {
|
|
27
|
+
/** The maximum number of words in an instruction. Default: 20 (STE Rule 5.1). */
|
|
28
|
+
readonly instructionWords? : number;
|
|
29
|
+
/** The maximum number of words in a description. Default: 25 (STE Rule 6.3). */
|
|
30
|
+
readonly descriptionWords? : number;
|
|
31
|
+
/** The maximum number of sentences in a paragraph. Default: 6 (STE Rule 6.6). */
|
|
32
|
+
readonly paragraphSentences? : number;
|
|
33
|
+
/** The maximum number of nouns in a noun cluster. Default: 3 (STE Rule 2.1). */
|
|
34
|
+
readonly nounClusterNouns? : number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The contents of `ste.config.json`. All properties are optional. */
|
|
38
|
+
export type SteConfigFile = {
|
|
39
|
+
readonly $schema? : string;
|
|
40
|
+
readonly include? : readonly string[];
|
|
41
|
+
readonly ignore? : readonly string[];
|
|
42
|
+
readonly technicalNouns? : readonly string[];
|
|
43
|
+
readonly technicalVerbs? : readonly string[];
|
|
44
|
+
readonly properNouns? : readonly string[];
|
|
45
|
+
readonly allowedWords? : readonly string[];
|
|
46
|
+
readonly rules? : Readonly<Record<string, RuleSetting>>;
|
|
47
|
+
readonly wordList? : readonly WordListEntryConfig[];
|
|
48
|
+
readonly limits? : LimitsConfig;
|
|
49
|
+
readonly dictionaryPath? : string | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type Limits = Required<LimitsConfig>;
|
|
53
|
+
|
|
54
|
+
export type ResolvedConfig = {
|
|
55
|
+
/** The directory that `include` and `ignore` are relative to. */
|
|
56
|
+
readonly root : string;
|
|
57
|
+
/** The configuration file, or null when the linter uses the defaults. */
|
|
58
|
+
readonly configFile : string | null;
|
|
59
|
+
readonly include : readonly string[];
|
|
60
|
+
readonly ignore : readonly string[];
|
|
61
|
+
readonly technicalNouns : readonly string[];
|
|
62
|
+
readonly technicalVerbs : readonly string[];
|
|
63
|
+
readonly properNouns : readonly string[];
|
|
64
|
+
readonly allowedWords : readonly string[];
|
|
65
|
+
/** The rule settings from the configuration. A rule without a setting uses its default severity. */
|
|
66
|
+
readonly rules : Readonly<Partial<Record<RuleId, RuleSetting>>>;
|
|
67
|
+
readonly wordList : readonly WordListEntry[];
|
|
68
|
+
readonly limits : Limits;
|
|
69
|
+
/** The absolute path of the local dictionary, or null. */
|
|
70
|
+
readonly dictionaryPath : string | null;
|
|
71
|
+
/** Where the dictionary path came from. */
|
|
72
|
+
readonly dictionarySource : "env" | "config" | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const DEFAULT_INCLUDE : readonly string[] = [
|
|
76
|
+
"README.md",
|
|
77
|
+
"docs/**/*.{md,mdx}",
|
|
78
|
+
"packages/*/README.md",
|
|
79
|
+
"packages/*/src/**/*.{ts,tsx}",
|
|
80
|
+
"packages/*/build/**/*.{ts,tsx}",
|
|
81
|
+
"packages/*/commands/**/*.{ts,tsx}",
|
|
82
|
+
"packages/*/*.config.{ts,mts}",
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
export const DEFAULT_IGNORE : readonly string[] = [
|
|
86
|
+
"**/node_modules/**",
|
|
87
|
+
"**/dist/**",
|
|
88
|
+
"**/*.d.ts",
|
|
89
|
+
"**/*.test.ts",
|
|
90
|
+
"**/*.test.tsx",
|
|
91
|
+
"**/*.spec.ts",
|
|
92
|
+
"**/*.spec.tsx",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export const DEFAULT_LIMITS : Limits = {
|
|
96
|
+
instructionWords : 20,
|
|
97
|
+
descriptionWords : 25,
|
|
98
|
+
paragraphSentences : 6,
|
|
99
|
+
nounClusterNouns : 3,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** An error in the configuration. */
|
|
103
|
+
export class ConfigError extends Error {
|
|
104
|
+
override readonly name : string = "ConfigError";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SETTINGS : readonly string[] = ["error", "warning", "off"];
|
|
108
|
+
|
|
109
|
+
const KNOWN_KEYS = new Set([
|
|
110
|
+
"$schema", "include", "ignore", "technicalNouns", "technicalVerbs", "properNouns", "allowedWords",
|
|
111
|
+
"rules", "wordList", "limits", "dictionaryPath",
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const WORD_LIST_KEYS = new Set(["id", "match", "suggest", "note", "severity", "verbOnly"]);
|
|
115
|
+
|
|
116
|
+
/** This function makes sure that `value` is a valid configuration. It throws a `ConfigError` that lists all problems. */
|
|
117
|
+
export function validateConfig(value : unknown, source : string) : SteConfigFile {
|
|
118
|
+
const problems : string[] = [];
|
|
119
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
120
|
+
throw new ConfigError(`${source}: the configuration must be a JSON object.`);
|
|
121
|
+
}
|
|
122
|
+
const record = value as Record<string, unknown>;
|
|
123
|
+
for (const key of Object.keys(record)) {
|
|
124
|
+
if (!KNOWN_KEYS.has(key)) problems.push(`"${key}" is not a known property. Known properties: ${[...KNOWN_KEYS].join(", ")}.`);
|
|
125
|
+
}
|
|
126
|
+
for (const key of ["include", "ignore", "technicalNouns", "technicalVerbs", "properNouns", "allowedWords"]) {
|
|
127
|
+
const item = record[key];
|
|
128
|
+
if (item !== undefined && !(Array.isArray(item) && item.every((entry) => typeof entry === "string"))) {
|
|
129
|
+
problems.push(`"${key}" must be an array of strings.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const rules = record["rules"];
|
|
133
|
+
if (rules !== undefined) {
|
|
134
|
+
if (typeof rules !== "object" || rules === null || Array.isArray(rules)) {
|
|
135
|
+
problems.push(`"rules" must be an object.`);
|
|
136
|
+
} else {
|
|
137
|
+
for (const [id, setting] of Object.entries(rules)) {
|
|
138
|
+
if (!isRuleId(id)) problems.push(`"rules.${id}" is not a known rule. Known rules: ${RULE_IDS.join(", ")}.`);
|
|
139
|
+
if (typeof setting !== "string" || !SETTINGS.includes(setting)) problems.push(`"rules.${id}" must be "error", "warning" or "off".`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const limits = record["limits"];
|
|
144
|
+
if (limits !== undefined) {
|
|
145
|
+
if (typeof limits !== "object" || limits === null || Array.isArray(limits)) {
|
|
146
|
+
problems.push(`"limits" must be an object.`);
|
|
147
|
+
} else {
|
|
148
|
+
for (const [key, limit] of Object.entries(limits)) {
|
|
149
|
+
if (!Object.hasOwn(DEFAULT_LIMITS, key)) problems.push(`"limits.${key}" is not a known limit. Known limits: ${Object.keys(DEFAULT_LIMITS).join(", ")}.`);
|
|
150
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1) problems.push(`"limits.${key}" must be an integer of 1 or more.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const dictionaryPath = record["dictionaryPath"];
|
|
155
|
+
if (dictionaryPath !== undefined && dictionaryPath !== null && typeof dictionaryPath !== "string") {
|
|
156
|
+
problems.push(`"dictionaryPath" must be a string or null.`);
|
|
157
|
+
}
|
|
158
|
+
const wordList = record["wordList"];
|
|
159
|
+
if (wordList !== undefined) {
|
|
160
|
+
if (!Array.isArray(wordList)) {
|
|
161
|
+
problems.push(`"wordList" must be an array.`);
|
|
162
|
+
} else {
|
|
163
|
+
wordList.forEach((item : unknown, index : number) => problems.push(...validateWordListEntry(item, `wordList[${index}]`)));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (problems.length > 0) throw new ConfigError(`${source}:\n - ${problems.join("\n - ")}`);
|
|
167
|
+
return record as SteConfigFile;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function validateWordListEntry(item : unknown, where : string) : string[] {
|
|
171
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) return [`"${where}" must be an object.`];
|
|
172
|
+
const problems : string[] = [];
|
|
173
|
+
const entry = item as Record<string, unknown>;
|
|
174
|
+
for (const key of Object.keys(entry)) {
|
|
175
|
+
if (!WORD_LIST_KEYS.has(key)) problems.push(`"${where}.${key}" is not a known property.`);
|
|
176
|
+
}
|
|
177
|
+
const match = entry["match"];
|
|
178
|
+
const matchValid = typeof match === "string" || (Array.isArray(match) && match.length > 0 && match.every((word) => typeof word === "string"));
|
|
179
|
+
if (match !== undefined && !matchValid) problems.push(`"${where}.match" must be a string or an array of strings.`);
|
|
180
|
+
if (entry["id"] !== undefined && typeof entry["id"] !== "string") problems.push(`"${where}.id" must be a string.`);
|
|
181
|
+
if (entry["id"] === undefined && match === undefined) problems.push(`"${where}" must have "id" or "match".`);
|
|
182
|
+
for (const key of ["suggest", "note"]) {
|
|
183
|
+
const text = entry[key];
|
|
184
|
+
if (text !== undefined && text !== null && typeof text !== "string") problems.push(`"${where}.${key}" must be a string or null.`);
|
|
185
|
+
}
|
|
186
|
+
const severity = entry["severity"];
|
|
187
|
+
if (severity !== undefined && (typeof severity !== "string" || !SETTINGS.includes(severity))) problems.push(`"${where}.severity" must be "error", "warning" or "off".`);
|
|
188
|
+
if (entry["verbOnly"] !== undefined && typeof entry["verbOnly"] !== "boolean") problems.push(`"${where}.verbOnly" must be true or false.`);
|
|
189
|
+
return problems;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* This function merges the default word list and the entries of the
|
|
194
|
+
* configuration. An entry with the ID (or the first `match` word) of a
|
|
195
|
+
* default entry changes that entry. The severity "off" removes the entry
|
|
196
|
+
* with that ID, and it removes the `match` words from all entries.
|
|
197
|
+
*/
|
|
198
|
+
export function mergeWordList(defaults : readonly WordListEntry[], overrides : readonly WordListEntryConfig[]) : WordListEntry[] {
|
|
199
|
+
const entries = new Map<string, WordListEntry | null>(defaults.map((entry) => [entry.id, entry]));
|
|
200
|
+
for (const override of overrides) {
|
|
201
|
+
const match = override.match === undefined ? undefined : typeof override.match === "string" ? [override.match] : [...override.match];
|
|
202
|
+
const id = override.id ?? match![0]!;
|
|
203
|
+
const base = entries.get(id) ?? null;
|
|
204
|
+
if (override.severity === "off") {
|
|
205
|
+
entries.set(id, null);
|
|
206
|
+
const removed = new Set((match ?? []).map((word) => word.toLowerCase()));
|
|
207
|
+
for (const [key, entry] of entries) {
|
|
208
|
+
if (entry === null) continue;
|
|
209
|
+
const kept = entry.match.filter((word) => !removed.has(word.toLowerCase()));
|
|
210
|
+
entries.set(key, kept.length === 0 ? null : { ...entry, match : kept });
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const merged : WordListEntry = {
|
|
215
|
+
id,
|
|
216
|
+
match : match ?? base?.match ?? [id],
|
|
217
|
+
suggest : override.suggest !== undefined ? override.suggest : base?.suggest ?? null,
|
|
218
|
+
note : override.note !== undefined ? override.note : base?.note ?? null,
|
|
219
|
+
severity : (override.severity as Severity | undefined) ?? base?.severity ?? "error",
|
|
220
|
+
verbOnly : override.verbOnly ?? base?.verbOnly ?? false,
|
|
221
|
+
context : base?.context ?? null,
|
|
222
|
+
};
|
|
223
|
+
entries.set(id, merged);
|
|
224
|
+
}
|
|
225
|
+
return [...entries.values()].filter((entry) : entry is WordListEntry => entry !== null);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export type BaseConfigOptions = {
|
|
229
|
+
/** The directory that `include` and `ignore` are relative to. Default: the empty string. */
|
|
230
|
+
readonly root? : string;
|
|
231
|
+
readonly configFile? : string | null;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* This function applies the defaults to a configuration file. It reads no
|
|
236
|
+
* file and no environment variable, thus the result has no dictionary path.
|
|
237
|
+
*/
|
|
238
|
+
export function baseConfig(file : SteConfigFile = {}, options : BaseConfigOptions = {}) : ResolvedConfig {
|
|
239
|
+
const rules : Partial<Record<RuleId, RuleSetting>> = {};
|
|
240
|
+
for (const [id, setting] of Object.entries(file.rules ?? {})) {
|
|
241
|
+
if (isRuleId(id)) rules[id] = setting;
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
root : options.root ?? "",
|
|
245
|
+
configFile : options.configFile ?? null,
|
|
246
|
+
include : file.include ?? DEFAULT_INCLUDE,
|
|
247
|
+
ignore : file.ignore ?? DEFAULT_IGNORE,
|
|
248
|
+
technicalNouns : file.technicalNouns ?? [],
|
|
249
|
+
technicalVerbs : file.technicalVerbs ?? [],
|
|
250
|
+
properNouns : file.properNouns ?? [],
|
|
251
|
+
allowedWords : file.allowedWords ?? [],
|
|
252
|
+
rules,
|
|
253
|
+
wordList : mergeWordList(DEFAULT_WORD_LIST, file.wordList ?? []),
|
|
254
|
+
limits : { ...DEFAULT_LIMITS, ...file.limits },
|
|
255
|
+
dictionaryPath : null,
|
|
256
|
+
dictionarySource : null,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
ConfigError,
|
|
7
|
+
DEFAULT_IGNORE,
|
|
8
|
+
DEFAULT_INCLUDE,
|
|
9
|
+
DEFAULT_LIMITS,
|
|
10
|
+
findConfigFile,
|
|
11
|
+
loadConfig,
|
|
12
|
+
mergeWordList,
|
|
13
|
+
resolveConfig,
|
|
14
|
+
resolvePath,
|
|
15
|
+
validateConfig,
|
|
16
|
+
} from "./config.js";
|
|
17
|
+
import { DEFAULT_WORD_LIST } from "./word-list.js";
|
|
18
|
+
|
|
19
|
+
let directory : string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
directory = mkdtempSync(join(tmpdir(), "ste-config-"));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
rmSync(directory, { recursive : true, force : true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("validateConfig", () => {
|
|
30
|
+
it("accepts a full configuration", () => {
|
|
31
|
+
const file = {
|
|
32
|
+
$schema : "./schema.json",
|
|
33
|
+
include : ["README.md"],
|
|
34
|
+
ignore : ["**/dist/**"],
|
|
35
|
+
technicalNouns : ["monitor"],
|
|
36
|
+
technicalVerbs : ["flush"],
|
|
37
|
+
properNouns : ["Grafana"],
|
|
38
|
+
allowedWords : ["okay"],
|
|
39
|
+
rules : { "modal-verb" : "warning", "noun-cluster" : "off" },
|
|
40
|
+
wordList : [{ match : ["blazingly fast"], suggest : "\"fast\"", severity : "warning", verbOnly : false, note : null }, { id : "via", severity : "off" }],
|
|
41
|
+
limits : { instructionWords : 18 },
|
|
42
|
+
dictionaryPath : null,
|
|
43
|
+
};
|
|
44
|
+
expect(validateConfig(file, "ste.config.json")).toBe(file);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("lists all problems in one error", () => {
|
|
48
|
+
const bad = {
|
|
49
|
+
unknown : 1,
|
|
50
|
+
include : "README.md",
|
|
51
|
+
rules : { "no-such-rule" : "error", "semicolon" : "fatal", "constructor" : "off" },
|
|
52
|
+
limits : { instructionWords : 0, constructor : 5, wordCount : 2 },
|
|
53
|
+
dictionaryPath : 3,
|
|
54
|
+
wordList : [{}, "x", { match : [], severity : "loud", verbOnly : "yes", id : 4, suggest : 5, extra : true }],
|
|
55
|
+
};
|
|
56
|
+
let message = "";
|
|
57
|
+
try {
|
|
58
|
+
validateConfig(bad, "ste.config.json");
|
|
59
|
+
} catch (error) {
|
|
60
|
+
expect(error).toBeInstanceOf(ConfigError);
|
|
61
|
+
message = (error as Error).message;
|
|
62
|
+
}
|
|
63
|
+
for (const part of [
|
|
64
|
+
"\"unknown\" is not a known property",
|
|
65
|
+
"\"include\" must be an array of strings",
|
|
66
|
+
"\"rules.no-such-rule\" is not a known rule",
|
|
67
|
+
"\"rules.semicolon\" must be \"error\", \"warning\" or \"off\"",
|
|
68
|
+
"\"rules.constructor\" is not a known rule",
|
|
69
|
+
"\"limits.instructionWords\" must be an integer of 1 or more",
|
|
70
|
+
"\"limits.constructor\" is not a known limit",
|
|
71
|
+
"\"limits.wordCount\" is not a known limit",
|
|
72
|
+
"\"dictionaryPath\" must be a string or null",
|
|
73
|
+
"\"wordList[0]\" must have \"id\" or \"match\"",
|
|
74
|
+
"\"wordList[1]\" must be an object",
|
|
75
|
+
"\"wordList[2].match\" must be a string or an array of strings",
|
|
76
|
+
"\"wordList[2].severity\"",
|
|
77
|
+
"\"wordList[2].verbOnly\" must be true or false",
|
|
78
|
+
"\"wordList[2].id\" must be a string",
|
|
79
|
+
"\"wordList[2].suggest\" must be a string or null",
|
|
80
|
+
"\"wordList[2].extra\" is not a known property",
|
|
81
|
+
]) {
|
|
82
|
+
expect(message).toContain(part);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("rejects values that are not objects", () => {
|
|
87
|
+
expect(() => validateConfig([], "x")).toThrow(ConfigError);
|
|
88
|
+
expect(() => validateConfig(null, "x")).toThrow("must be a JSON object");
|
|
89
|
+
expect(() => validateConfig({ rules : [] }, "x")).toThrow("\"rules\" must be an object");
|
|
90
|
+
expect(() => validateConfig({ limits : 3 }, "x")).toThrow("\"limits\" must be an object");
|
|
91
|
+
expect(() => validateConfig({ wordList : {} }, "x")).toThrow("\"wordList\" must be an array");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("resolveConfig", () => {
|
|
96
|
+
it("applies the defaults", () => {
|
|
97
|
+
const config = resolveConfig({}, { root : directory });
|
|
98
|
+
expect(config.include).toBe(DEFAULT_INCLUDE);
|
|
99
|
+
expect(config.ignore).toBe(DEFAULT_IGNORE);
|
|
100
|
+
expect(config.limits).toEqual(DEFAULT_LIMITS);
|
|
101
|
+
expect(config.wordList).toEqual(DEFAULT_WORD_LIST);
|
|
102
|
+
expect(config.dictionaryPath).toBeNull();
|
|
103
|
+
expect(config.dictionarySource).toBeNull();
|
|
104
|
+
expect(config.configFile).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("keeps known rule settings and merges the limits", () => {
|
|
108
|
+
const config = resolveConfig({ rules : { "modal-verb" : "warning" }, limits : { paragraphSentences : 4 } }, { root : directory });
|
|
109
|
+
expect(config.rules).toEqual({ "modal-verb" : "warning" });
|
|
110
|
+
expect(config.limits).toEqual({ ...DEFAULT_LIMITS, paragraphSentences : 4 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("resolves the dictionary path from the configuration, relative to the root", () => {
|
|
114
|
+
const config = resolveConfig({ dictionaryPath : ".ste/dictionary.json" }, { root : directory });
|
|
115
|
+
expect(config.dictionaryPath).toBe(resolve(directory, ".ste/dictionary.json"));
|
|
116
|
+
expect(config.dictionarySource).toBe("config");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("gives the environment variable priority, relative to the working directory", () => {
|
|
120
|
+
const config = resolveConfig({ dictionaryPath : "a.json" }, { root : directory, cwd : "/work", env : { STE_DICTIONARY : "b.json" } });
|
|
121
|
+
expect(config.dictionaryPath).toBe(resolve("/work", "b.json"));
|
|
122
|
+
expect(config.dictionarySource).toBe("env");
|
|
123
|
+
expect(resolveConfig({}, { root : directory, env : { STE_DICTIONARY : " " } }).dictionaryPath).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("expands ~ to the home directory", () => {
|
|
127
|
+
expect(resolvePath("~/ste/dictionary.json", "/base")).toBe(join(homedir(), "ste/dictionary.json"));
|
|
128
|
+
expect(resolvePath("~", "/base")).toBe(homedir());
|
|
129
|
+
expect(resolvePath("/abs/file.json", "/base")).toBe(resolve("/abs/file.json"));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("mergeWordList", () => {
|
|
134
|
+
it("changes, adds and removes entries", () => {
|
|
135
|
+
const merged = mergeWordList(DEFAULT_WORD_LIST, [
|
|
136
|
+
{ id : "via", suggest : "\"by\"", severity : "warning" },
|
|
137
|
+
{ match : "blazingly fast", suggest : "\"fast\"" },
|
|
138
|
+
{ id : "utilize", severity : "off" },
|
|
139
|
+
{ match : ["easily"], severity : "off" },
|
|
140
|
+
]);
|
|
141
|
+
const byId = new Map(merged.map((entry) => [entry.id, entry]));
|
|
142
|
+
expect(byId.get("via")).toMatchObject({ suggest : "\"by\"", severity : "warning", match : ["via"] });
|
|
143
|
+
expect(byId.get("blazingly fast")).toMatchObject({ match : ["blazingly fast"], suggest : "\"fast\"", severity : "error", verbOnly : false, note : null });
|
|
144
|
+
expect(byId.has("utilize")).toBe(false);
|
|
145
|
+
expect(byId.get("simple")!.match).toEqual(["simple", "simply"]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("removes an entry that has no words left", () => {
|
|
149
|
+
const merged = mergeWordList(DEFAULT_WORD_LIST, [{ match : ["numerous"], severity : "off" }]);
|
|
150
|
+
expect(merged.some((entry) => entry.id === "numerous")).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("keeps the fields of a default entry that the override does not give", () => {
|
|
154
|
+
const merged = mergeWordList(DEFAULT_WORD_LIST, [{ id : "run", note : "Extra." }]);
|
|
155
|
+
expect(merged.find((entry) => entry.id === "run")).toMatchObject({ verbOnly : true, severity : "warning", note : "Extra." });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("loadConfig", () => {
|
|
160
|
+
it("finds ste.config.json in a parent directory and uses its directory as the root", () => {
|
|
161
|
+
writeFileSync(join(directory, "ste.config.json"), JSON.stringify({ technicalNouns : ["monitor"], dictionaryPath : "dict.json" }));
|
|
162
|
+
const nested = join(directory, "a", "b");
|
|
163
|
+
mkdirSync(nested, { recursive : true });
|
|
164
|
+
expect(findConfigFile(nested)).toBe(join(directory, "ste.config.json"));
|
|
165
|
+
const config = loadConfig({ cwd : nested });
|
|
166
|
+
expect(config.root).toBe(directory);
|
|
167
|
+
expect(config.technicalNouns).toEqual(["monitor"]);
|
|
168
|
+
expect(config.dictionaryPath).toBe(join(directory, "dict.json"));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("uses the defaults without a configuration file", () => {
|
|
172
|
+
const config = loadConfig({ cwd : directory, env : {} });
|
|
173
|
+
expect(config.configFile === null || !config.configFile.startsWith(directory)).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("reads an explicit configuration file", () => {
|
|
177
|
+
writeFileSync(join(directory, "custom.json"), JSON.stringify({ rules : { semicolon : "warning" } }));
|
|
178
|
+
expect(loadConfig({ cwd : directory, configPath : "custom.json" }).rules).toEqual({ semicolon : "warning" });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("gives clear errors for a missing file, a file that is not JSON and a configuration that is not valid", () => {
|
|
182
|
+
expect(() => loadConfig({ cwd : directory, configPath : "missing.json" })).toThrow("Cannot read the configuration file");
|
|
183
|
+
writeFileSync(join(directory, "broken.json"), "{ nope");
|
|
184
|
+
expect(() => loadConfig({ cwd : directory, configPath : "broken.json" })).toThrow("is not valid JSON");
|
|
185
|
+
writeFileSync(join(directory, "invalid.json"), JSON.stringify({ rules : { nope : "error" } }));
|
|
186
|
+
expect(() => loadConfig({ cwd : directory, configPath : "invalid.json" })).toThrow("is not a known rule");
|
|
187
|
+
});
|
|
188
|
+
});
|