@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,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grammar heuristics. The linter has no part-of-speech tagger. These
|
|
3
|
+
* functions look at the words near a word to guess its function. Each
|
|
4
|
+
* heuristic prefers to miss a problem than to report a false one.
|
|
5
|
+
*/
|
|
6
|
+
import { isCounted, isNumber, isWord } from "./tokens.js";
|
|
7
|
+
import { BE_FORMS, CLAUSE_STARTERS, CONDITION_MARKERS, CONJUNCTIONS, DETERMINERS, DO_FORMS, HAVE_FORMS, IRREGULAR_PARTICIPLES, LEADING_ADVERBS, MIDDLE_ADVERBS, MODALS, NOT_PARTICIPLE_ED, OBJECT_PRONOUNS, PARTICLES, PASSIVE_AUXILIARIES, PREPOSITIONS, SUBJECT_PRONOUNS, } from "./words.js";
|
|
8
|
+
/**
|
|
9
|
+
* The possible base forms of a word: the word itself, and the word without
|
|
10
|
+
* the endings -s, -es, -ies, -ed, -d, -ied and -ing. The list can contain
|
|
11
|
+
* forms that are not words. The callers look up each form in a word list.
|
|
12
|
+
*/
|
|
13
|
+
export function lemmas(word) {
|
|
14
|
+
const forms = new Set([word]);
|
|
15
|
+
const add = (form) => {
|
|
16
|
+
if (form.length >= 2)
|
|
17
|
+
forms.add(form);
|
|
18
|
+
};
|
|
19
|
+
if (word.endsWith("'s"))
|
|
20
|
+
add(word.slice(0, -2));
|
|
21
|
+
if (word.endsWith("ies"))
|
|
22
|
+
add(word.slice(0, -3) + "y");
|
|
23
|
+
if (word.endsWith("es"))
|
|
24
|
+
add(word.slice(0, -2));
|
|
25
|
+
if (word.endsWith("s") && !word.endsWith("ss"))
|
|
26
|
+
add(word.slice(0, -1));
|
|
27
|
+
if (word.endsWith("ied"))
|
|
28
|
+
add(word.slice(0, -3) + "y");
|
|
29
|
+
if (word.endsWith("ed")) {
|
|
30
|
+
const stem = word.slice(0, -2);
|
|
31
|
+
add(stem);
|
|
32
|
+
add(stem + "e");
|
|
33
|
+
if (/([^aeiou])\1$/.test(stem))
|
|
34
|
+
add(stem.slice(0, -1));
|
|
35
|
+
}
|
|
36
|
+
if (word.endsWith("ing")) {
|
|
37
|
+
const stem = word.slice(0, -3);
|
|
38
|
+
add(stem);
|
|
39
|
+
add(stem + "e");
|
|
40
|
+
if (/([^aeiou])\1$/.test(stem))
|
|
41
|
+
add(stem.slice(0, -1));
|
|
42
|
+
if (stem.endsWith("y"))
|
|
43
|
+
add(stem.slice(0, -1) + "ie");
|
|
44
|
+
}
|
|
45
|
+
return [...forms];
|
|
46
|
+
}
|
|
47
|
+
/** True when one of the base forms of `word` is in `words`. */
|
|
48
|
+
export function hasLemmaIn(word, words) {
|
|
49
|
+
return lemmas(word).some((form) => words.has(form));
|
|
50
|
+
}
|
|
51
|
+
/** True when `word` can be a past participle: an irregular participle, or a word that ends in -ed. */
|
|
52
|
+
export function isParticiple(word) {
|
|
53
|
+
if (IRREGULAR_PARTICIPLES.has(word))
|
|
54
|
+
return true;
|
|
55
|
+
return /^[a-z]{2,}ed$/.test(word) && !NOT_PARTICIPLE_ED.has(word);
|
|
56
|
+
}
|
|
57
|
+
/** True for a word that is an auxiliary verb: a form of "be", "have" or "do", or a modal verb. */
|
|
58
|
+
export function isAuxiliary(token) {
|
|
59
|
+
return isWord(token) && (BE_FORMS.has(token.lower) || HAVE_FORMS.has(token.lower) || DO_FORMS.has(token.lower) || MODALS.has(token.lower));
|
|
60
|
+
}
|
|
61
|
+
/** The index of the next token that is a word or a placeholder, or -1. */
|
|
62
|
+
export function nextCounted(tokens, from) {
|
|
63
|
+
for (let i = from; i < tokens.length; i++)
|
|
64
|
+
if (isCounted(tokens[i]))
|
|
65
|
+
return i;
|
|
66
|
+
return -1;
|
|
67
|
+
}
|
|
68
|
+
/** The previous token, or undefined at the start. */
|
|
69
|
+
function before(tokens, index) {
|
|
70
|
+
return tokens[index - 1];
|
|
71
|
+
}
|
|
72
|
+
/** True when the token can start the object of a verb: a determiner, a pronoun, a number, a placeholder or an adverb. */
|
|
73
|
+
export function startsObject(token) {
|
|
74
|
+
if (token === undefined)
|
|
75
|
+
return false;
|
|
76
|
+
if (token.kind === "code")
|
|
77
|
+
return true;
|
|
78
|
+
if (!isWord(token))
|
|
79
|
+
return false;
|
|
80
|
+
return DETERMINERS.has(token.lower)
|
|
81
|
+
|| OBJECT_PRONOUNS.has(token.lower)
|
|
82
|
+
|| isNumber(token)
|
|
83
|
+
|| token.lower.endsWith("ly")
|
|
84
|
+
|| ["whether", "if", "how", "what", "when", "where", "which", "why", "who"].includes(token.lower);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* True when the sentence is an instruction (imperative). The heuristic:
|
|
88
|
+
*
|
|
89
|
+
* 1. Skip the adverbs at the start, for example "then", "first" and "please".
|
|
90
|
+
* 2. If the sentence starts with a condition or purpose clause ("If ...,",
|
|
91
|
+
* "To ...,", "When ...,"), continue after the first comma.
|
|
92
|
+
* 3. "Do not", "Never", "Make sure" and "Be sure" start an instruction.
|
|
93
|
+
* 4. Otherwise, the first word must be the base form of a known verb. The
|
|
94
|
+
* next words must not show that the first word is a noun subject. Thus,
|
|
95
|
+
* the next word is not an auxiliary verb or "of". Also, the next two
|
|
96
|
+
* words are not a plural noun and a verb ("Test runs take 5 s").
|
|
97
|
+
*/
|
|
98
|
+
export function isInstruction(tokens, options) {
|
|
99
|
+
let k = nextCounted(tokens, 0);
|
|
100
|
+
if (k === -1)
|
|
101
|
+
return false;
|
|
102
|
+
const skipAdverbs = () => {
|
|
103
|
+
while (k !== -1 && isWord(tokens[k]) && LEADING_ADVERBS.has(tokens[k].lower))
|
|
104
|
+
k = nextCounted(tokens, k + 1);
|
|
105
|
+
};
|
|
106
|
+
skipAdverbs();
|
|
107
|
+
if (k === -1)
|
|
108
|
+
return false;
|
|
109
|
+
if (isWord(tokens[k]) && CLAUSE_STARTERS.has(tokens[k].lower)) {
|
|
110
|
+
const comma = tokens.findIndex((token, index) => index > k && token.kind === "punct" && token.text === ",");
|
|
111
|
+
if (comma === -1)
|
|
112
|
+
return false;
|
|
113
|
+
k = nextCounted(tokens, comma + 1);
|
|
114
|
+
skipAdverbs();
|
|
115
|
+
if (k === -1)
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const word = tokens[k];
|
|
119
|
+
if (!isWord(word))
|
|
120
|
+
return false;
|
|
121
|
+
const n1 = tokens[k + 1];
|
|
122
|
+
const n2 = tokens[k + 2];
|
|
123
|
+
if (word.lower === "never" || word.lower === "don't")
|
|
124
|
+
return true;
|
|
125
|
+
if (word.lower === "do" && isWord(n1) && n1.lower === "not")
|
|
126
|
+
return true;
|
|
127
|
+
if (word.lower === "make" && isWord(n1) && n1.lower === "sure")
|
|
128
|
+
return true;
|
|
129
|
+
if (word.lower === "be" && isWord(n1) && ["sure", "careful", "aware"].includes(n1.lower))
|
|
130
|
+
return true;
|
|
131
|
+
if (!options.verbs.has(word.lower))
|
|
132
|
+
return false;
|
|
133
|
+
if (isWord(n1)) {
|
|
134
|
+
if (isAuxiliary(n1) || n1.lower === "of")
|
|
135
|
+
return false;
|
|
136
|
+
if (/[^s]s$/.test(n1.lower) && isWord(n2) && (isAuxiliary(n2) || options.verbs.has(n2.lower)))
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* True when the word at `index` is likely a verb, not a noun or an
|
|
143
|
+
* adjective. The heuristic is conservative. It is true only when the words
|
|
144
|
+
* near the word show a verb. It looks at these words:
|
|
145
|
+
*
|
|
146
|
+
* - The word before. These words show a verb: "to", a modal verb, a form
|
|
147
|
+
* of "do", a subject pronoun, a form of "be" or "have" (for participles).
|
|
148
|
+
* - The start of the sentence, for an imperative
|
|
149
|
+
* - The word after, for an -s form: an object, for example "the", a
|
|
150
|
+
* pronoun, code or a number
|
|
151
|
+
* - A determiner before the word, which shows a noun ("the call", "a run").
|
|
152
|
+
*/
|
|
153
|
+
export function isVerbUse(tokens, index, options) {
|
|
154
|
+
const word = tokens[index];
|
|
155
|
+
if (!isWord(word))
|
|
156
|
+
return false;
|
|
157
|
+
const previous = before(tokens, index);
|
|
158
|
+
const next = tokens[index + 1];
|
|
159
|
+
const lower = word.lower;
|
|
160
|
+
const previousLower = isWord(previous) ? previous.lower : null;
|
|
161
|
+
if (previousLower !== null && (DETERMINERS.has(previousLower) || isNumber(previous)))
|
|
162
|
+
return false;
|
|
163
|
+
if (lower.endsWith("ing"))
|
|
164
|
+
return isIngVerbUse(tokens, index);
|
|
165
|
+
if ((lower.endsWith("ed") || isParticiple(lower)) && previousLower !== null && (PASSIVE_AUXILIARIES.has(previousLower) || HAVE_FORMS.has(previousLower)))
|
|
166
|
+
return true;
|
|
167
|
+
if (lower.endsWith("ed")) {
|
|
168
|
+
const subject = (previousLower !== null && SUBJECT_PRONOUNS.has(previousLower)) || previous?.kind === "code";
|
|
169
|
+
return subject && (startsObject(next) || (isWord(next) && PREPOSITIONS.has(next.lower)));
|
|
170
|
+
}
|
|
171
|
+
// An irregular participle such as "run" or "set" is also a base form, so the base form tests apply to it.
|
|
172
|
+
if (previousLower !== null) {
|
|
173
|
+
if (previousLower === "to" || MODALS.has(previousLower) || DO_FORMS.has(previousLower) || previousLower === "not")
|
|
174
|
+
return true;
|
|
175
|
+
if (["you", "we", "they", "i", "please", "and", "or", "then", "also", "always", "first"].includes(previousLower))
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
// A clause start, or an adverb at a clause start: "Run the tests", "Simply run the tests".
|
|
179
|
+
const clauseStart = isClauseStart(tokens, index)
|
|
180
|
+
|| (previousLower !== null && LEADING_ADVERBS.has(previousLower) && isClauseStart(tokens, index - 1));
|
|
181
|
+
if (clauseStart) {
|
|
182
|
+
// "Run time" can be a noun, so a plain word after the verb is not sufficient.
|
|
183
|
+
if (!options.verbs.has(lower))
|
|
184
|
+
return false;
|
|
185
|
+
return startsObject(next) || (isWord(next) && (PREPOSITIONS.has(next.lower) || PARTICLES.has(next.lower)));
|
|
186
|
+
}
|
|
187
|
+
if (/[^s]s$/.test(lower)) {
|
|
188
|
+
if (!lemmas(lower).some((form) => form !== lower && options.verbs.has(form)))
|
|
189
|
+
return false;
|
|
190
|
+
// After "it", "which" or "that", an -s form is a verb: "it runs", "which calls".
|
|
191
|
+
if (previousLower !== null && SUBJECT_PRONOUNS.has(previousLower))
|
|
192
|
+
return true;
|
|
193
|
+
const subjectBefore = previous !== undefined && (previous.kind === "code" || (isWord(previous) && !PREPOSITIONS.has(previous.lower) && !CONJUNCTIONS.has(previous.lower)));
|
|
194
|
+
const objectAfter = startsObject(next) || (isWord(next) && (PREPOSITIONS.has(next.lower) || PARTICLES.has(next.lower)));
|
|
195
|
+
return subjectBefore && objectAfter;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* True when the -ing word at `index` is a verb. The heuristic finds four
|
|
201
|
+
* uses:
|
|
202
|
+
*
|
|
203
|
+
* - After a form of "be": "is running", "is still running"
|
|
204
|
+
* - After a preposition, with an object: "by calling `stop()`", "after
|
|
205
|
+
* waiting for the worker"
|
|
206
|
+
* - At the start of a clause, with an object: "Using the worker, ..."
|
|
207
|
+
* - After a word that is not a determiner, with an object (a reduced
|
|
208
|
+
* clause): "a timer firing every 5 ms".
|
|
209
|
+
*
|
|
210
|
+
* A noun ("the timing") or an adjective before a noun ("existing code")
|
|
211
|
+
* is not a verb use.
|
|
212
|
+
*/
|
|
213
|
+
export function isIngVerbUse(tokens, index) {
|
|
214
|
+
const previous = tokens[index - 1];
|
|
215
|
+
const next = tokens[index + 1];
|
|
216
|
+
const objectAfter = startsObject(next) || (isWord(next) && (PREPOSITIONS.has(next.lower) || PARTICLES.has(next.lower)));
|
|
217
|
+
if (isWord(previous)) {
|
|
218
|
+
if (BE_FORMS.has(previous.lower))
|
|
219
|
+
return true;
|
|
220
|
+
const beforePrevious = tokens[index - 2];
|
|
221
|
+
if (MIDDLE_ADVERBS.has(previous.lower) && isWord(beforePrevious) && BE_FORMS.has(beforePrevious.lower))
|
|
222
|
+
return true;
|
|
223
|
+
if (PREPOSITIONS.has(previous.lower))
|
|
224
|
+
return objectAfter;
|
|
225
|
+
}
|
|
226
|
+
if (isClauseStart(tokens, index))
|
|
227
|
+
return startsObject(next);
|
|
228
|
+
if (isWord(previous) && !DETERMINERS.has(previous.lower) && !CONJUNCTIONS.has(previous.lower))
|
|
229
|
+
return startsObject(next);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
/** True when the token is the first word of the sentence or the first word after a comma, colon or opening parenthesis. */
|
|
233
|
+
export function isClauseStart(tokens, index) {
|
|
234
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
235
|
+
const token = tokens[i];
|
|
236
|
+
if (isCounted(token))
|
|
237
|
+
return false;
|
|
238
|
+
if ([",", ":", "(", ";", "—", "–"].includes(token.text))
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
const OBLIGATION_MODALS = new Set(["must", "should", "shall", "mustn't", "shouldn't"]);
|
|
244
|
+
/** This function finds the passive constructions in a sentence: a form of "be" or "get" and a past participle. */
|
|
245
|
+
export function findPassives(tokens) {
|
|
246
|
+
const passives = [];
|
|
247
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
248
|
+
const token = tokens[i];
|
|
249
|
+
if (!isWord(token) || !PASSIVE_AUXILIARIES.has(token.lower))
|
|
250
|
+
continue;
|
|
251
|
+
let j = i + 1;
|
|
252
|
+
let skipped = 0;
|
|
253
|
+
while (skipped < 2 && isWord(tokens[j]) && MIDDLE_ADVERBS.has(tokens[j].lower) && !isParticiple(tokens[j].lower)) {
|
|
254
|
+
j++;
|
|
255
|
+
skipped++;
|
|
256
|
+
}
|
|
257
|
+
const participle = tokens[j];
|
|
258
|
+
if (!isWord(participle) || !isParticiple(participle.lower))
|
|
259
|
+
continue;
|
|
260
|
+
const previous = isWord(tokens[i - 1]) ? tokens[i - 1].lower : null;
|
|
261
|
+
const previous2 = isWord(tokens[i - 2]) ? tokens[i - 2].lower : null;
|
|
262
|
+
const viaTo = previous === "to" && previous2 !== null && ["has", "have", "had", "need", "needs", "needed", "is", "are", "was", "were", "ought"].includes(previous2);
|
|
263
|
+
const obligation = (previous !== null && OBLIGATION_MODALS.has(previous)) || viaTo;
|
|
264
|
+
const modal = obligation || (previous !== null && (MODALS.has(previous) || previous === "to"));
|
|
265
|
+
const after = tokens[j + 1];
|
|
266
|
+
passives.push({
|
|
267
|
+
start: viaTo ? i - 2 : modal ? i - 1 : i,
|
|
268
|
+
auxiliary: i,
|
|
269
|
+
participle: j,
|
|
270
|
+
obligation,
|
|
271
|
+
modal,
|
|
272
|
+
agent: isWord(after) && after.lower === "by",
|
|
273
|
+
inCondition: hasConditionBefore(tokens, i),
|
|
274
|
+
});
|
|
275
|
+
i = j;
|
|
276
|
+
}
|
|
277
|
+
return passives;
|
|
278
|
+
}
|
|
279
|
+
function hasConditionBefore(tokens, index) {
|
|
280
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
281
|
+
const token = tokens[i];
|
|
282
|
+
if (token.kind === "punct" && [",", ";", ":"].includes(token.text))
|
|
283
|
+
return false;
|
|
284
|
+
if (isWord(token) && CONDITION_MARKERS.has(token.lower))
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=grammar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar.js","sourceRoot":"","sources":["../../src/text/grammar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Token } from "./tokens.js";
|
|
2
|
+
export type PhraseMatcherOptions = {
|
|
3
|
+
/** Match the case of each letter. For example, the name "Event Timing" must not match "event timing". */
|
|
4
|
+
readonly caseSensitive?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A set of words and multi-word phrases. A phrase matches consecutive word
|
|
8
|
+
* tokens, without case by default. Punctuation and placeholders between the
|
|
9
|
+
* words stop a match.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PhraseMatcher<T = true> {
|
|
12
|
+
private readonly byFirstWord;
|
|
13
|
+
private readonly caseSensitive;
|
|
14
|
+
constructor(options?: PhraseMatcherOptions);
|
|
15
|
+
private key;
|
|
16
|
+
/** This method adds a phrase. A later phrase with the same words replaces the earlier one. */
|
|
17
|
+
add(phrase: string, value: T): void;
|
|
18
|
+
/** The longest phrase that starts at `tokens[index]`, or null. */
|
|
19
|
+
match(tokens: readonly Token[], index: number): {
|
|
20
|
+
readonly length: number;
|
|
21
|
+
readonly value: T;
|
|
22
|
+
} | null;
|
|
23
|
+
/** The number of tokens in the longest phrase that starts at `tokens[index]`, or 0. */
|
|
24
|
+
longestMatch(tokens: readonly Token[], index: number): number;
|
|
25
|
+
/** True when the matcher has no phrases. */
|
|
26
|
+
get empty(): boolean;
|
|
27
|
+
}
|
|
28
|
+
/** The lower-case words of a phrase. */
|
|
29
|
+
export declare function splitPhrase(phrase: string): string[];
|
|
30
|
+
//# sourceMappingURL=phrases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../../src/text/phrases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,MAAM,oBAAoB,GAAG;IAC/B,yGAAyG;IACzG,QAAQ,CAAC,aAAa,CAAC,EAAG,OAAO,CAAC;CACrC,CAAC;AAEF;;;;GAIG;AACH,qBAAa,aAAa,CAAC,CAAC,GAAG,IAAI;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwF;IACpH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;gBAE7B,OAAO,GAAG,oBAAyB;IAI/C,OAAO,CAAC,GAAG;IAIX,8FAA8F;IAC9F,GAAG,CAAC,MAAM,EAAG,MAAM,EAAE,KAAK,EAAG,CAAC,GAAI,IAAI;IAYtC,kEAAkE;IAClE,KAAK,CAAC,MAAM,EAAG,SAAS,KAAK,EAAE,EAAE,KAAK,EAAG,MAAM,GAAI;QAAE,QAAQ,CAAC,MAAM,EAAG,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAG,CAAC,CAAA;KAAE,GAAG,IAAI;IAmB1G,uFAAuF;IACvF,YAAY,CAAC,MAAM,EAAG,SAAS,KAAK,EAAE,EAAE,KAAK,EAAG,MAAM,GAAI,MAAM;IAIhE,4CAA4C;IAC5C,IAAI,KAAK,IAAK,OAAO,CAEpB;CACJ;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,MAAM,EAAG,MAAM,GAAI,MAAM,EAAE,CAEtD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isWord, normalizeWord } from "./tokens.js";
|
|
2
|
+
/**
|
|
3
|
+
* A set of words and multi-word phrases. A phrase matches consecutive word
|
|
4
|
+
* tokens, without case by default. Punctuation and placeholders between the
|
|
5
|
+
* words stop a match.
|
|
6
|
+
*/
|
|
7
|
+
export class PhraseMatcher {
|
|
8
|
+
byFirstWord = new Map();
|
|
9
|
+
caseSensitive;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.caseSensitive = options.caseSensitive ?? false;
|
|
12
|
+
}
|
|
13
|
+
key(text) {
|
|
14
|
+
return this.caseSensitive ? text.replace(/[’‘]/g, "'") : normalizeWord(text);
|
|
15
|
+
}
|
|
16
|
+
/** This method adds a phrase. A later phrase with the same words replaces the earlier one. */
|
|
17
|
+
add(phrase, value) {
|
|
18
|
+
const words = phrase.split(/\s+/).filter((word) => word.length > 0).map((word) => this.key(word));
|
|
19
|
+
if (words.length === 0)
|
|
20
|
+
return;
|
|
21
|
+
const first = words[0];
|
|
22
|
+
const list = this.byFirstWord.get(first) ?? [];
|
|
23
|
+
const existing = list.findIndex((entry) => entry.words.join(" ") === words.join(" "));
|
|
24
|
+
if (existing >= 0)
|
|
25
|
+
list.splice(existing, 1);
|
|
26
|
+
list.push({ words, value });
|
|
27
|
+
list.sort((a, b) => b.words.length - a.words.length);
|
|
28
|
+
this.byFirstWord.set(first, list);
|
|
29
|
+
}
|
|
30
|
+
/** The longest phrase that starts at `tokens[index]`, or null. */
|
|
31
|
+
match(tokens, index) {
|
|
32
|
+
const first = tokens[index];
|
|
33
|
+
if (!isWord(first))
|
|
34
|
+
return null;
|
|
35
|
+
const candidates = this.byFirstWord.get(this.key(first.text));
|
|
36
|
+
if (candidates === undefined)
|
|
37
|
+
return null;
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
let matched = true;
|
|
40
|
+
for (let k = 1; k < candidate.words.length; k++) {
|
|
41
|
+
const token = tokens[index + k];
|
|
42
|
+
if (!isWord(token) || this.key(token.text) !== candidate.words[k]) {
|
|
43
|
+
matched = false;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (matched)
|
|
48
|
+
return { length: candidate.words.length, value: candidate.value };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/** The number of tokens in the longest phrase that starts at `tokens[index]`, or 0. */
|
|
53
|
+
longestMatch(tokens, index) {
|
|
54
|
+
return this.match(tokens, index)?.length ?? 0;
|
|
55
|
+
}
|
|
56
|
+
/** True when the matcher has no phrases. */
|
|
57
|
+
get empty() {
|
|
58
|
+
return this.byFirstWord.size === 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** The lower-case words of a phrase. */
|
|
62
|
+
export function splitPhrase(phrase) {
|
|
63
|
+
return normalizeWord(phrase).split(/\s+/).filter((word) => word.length > 0);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=phrases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phrases.js","sourceRoot":"","sources":["../../src/text/phrases.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentence division and STE word counting (STE Rules 8.4 to 8.7).
|
|
3
|
+
*/
|
|
4
|
+
import type { PhraseMatcher } from "./phrases.js";
|
|
5
|
+
import { type Token } from "./tokens.js";
|
|
6
|
+
/** A range of tokens: `from` is the first token, `to` is the token after the last one. */
|
|
7
|
+
export type TokenRange = {
|
|
8
|
+
readonly from: number;
|
|
9
|
+
readonly to: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* This function divides tokens into sentences. A sentence stops at `.`,
|
|
13
|
+
* `!`, `?` or an ellipsis, when white space comes next. After the white
|
|
14
|
+
* space, there must be a capital letter, a digit, a placeholder, or an
|
|
15
|
+
* opening bracket or quote. Closing brackets and quotes after the stop stay
|
|
16
|
+
* in the sentence. A period after an abbreviation, for example `e.g.`, does
|
|
17
|
+
* not stop a sentence.
|
|
18
|
+
*/
|
|
19
|
+
export declare function splitSentences(tokens: readonly Token[]): TokenRange[];
|
|
20
|
+
/** The pairs of matching parentheses that no other pair contains. A parenthesis without a partner is ignored. */
|
|
21
|
+
export declare function topLevelParentheses(tokens: readonly Token[]): TokenRange[];
|
|
22
|
+
export type WordCount = {
|
|
23
|
+
/** The STE word count of the sentence. */
|
|
24
|
+
readonly words: number;
|
|
25
|
+
/** The tokens in each top-level parenthesis that has words. Each one is a separate sentence (STE Rule 8.5). */
|
|
26
|
+
readonly parentheticals: readonly (readonly Token[])[];
|
|
27
|
+
};
|
|
28
|
+
export type CountOptions = {
|
|
29
|
+
/** Multi-word proper nouns and technical nouns that count as one word. */
|
|
30
|
+
readonly properNouns: PhraseMatcher;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* This function counts the words of a sentence as STE Rules 8.5 to 8.7
|
|
34
|
+
* tell:
|
|
35
|
+
*
|
|
36
|
+
* - A placeholder (code span, link tag, URL, quoted text) is one word.
|
|
37
|
+
* - A hyphenated word is one word.
|
|
38
|
+
* - A number and its unit, for example `20 ms`, are one word.
|
|
39
|
+
* - A multi-word proper noun from the glossary is one word.
|
|
40
|
+
* - Two or more words with capital letters after the start of the
|
|
41
|
+
* sentence are a name or a title. They count as one word.
|
|
42
|
+
* - Text in parentheses is one word. Its words make a separate sentence.
|
|
43
|
+
*/
|
|
44
|
+
export declare function countWords(tokens: readonly Token[], options: CountOptions): WordCount;
|
|
45
|
+
//# sourceMappingURL=sentences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentences.d.ts","sourceRoot":"","sources":["../../src/text/sentences.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAA8C,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAGrF,0FAA0F;AAC1F,MAAM,MAAM,UAAU,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAG,MAAM,CAAC;CACxB,CAAC;AAyBF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAG,SAAS,KAAK,EAAE,GAAI,UAAU,EAAE,CAsBvE;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,MAAM,EAAG,SAAS,KAAK,EAAE,GAAI,UAAU,EAAE,CAa5E;AAED,MAAM,MAAM,SAAS,GAAG;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAG,MAAM,CAAC;IACxB,+GAA+G;IAC/G,QAAQ,CAAC,cAAc,EAAG,SAAS,CAAC,SAAS,KAAK,EAAE,CAAC,EAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAG,aAAa,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAG,SAAS,KAAK,EAAE,EAAE,OAAO,EAAG,YAAY,GAAI,SAAS,CAsCxF"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentence division and STE word counting (STE Rules 8.4 to 8.7).
|
|
3
|
+
*/
|
|
4
|
+
import { isCapitalized, isCounted, isNumber, isWord } from "./tokens.js";
|
|
5
|
+
import { NO_BREAK_ABBREVIATIONS, NUMBER_ABBREVIATIONS, UNITS } from "./words.js";
|
|
6
|
+
const TERMINALS = new Set([".", "!", "?", "...", "…"]);
|
|
7
|
+
const CLOSERS = new Set([")", "]", "}", "\"", "”", "’", "'"]);
|
|
8
|
+
const OPENERS = new Set(["(", "[", "\"", "“", "‘", "'"]);
|
|
9
|
+
function startsSentence(token) {
|
|
10
|
+
if (token.kind === "code")
|
|
11
|
+
return true;
|
|
12
|
+
if (token.kind === "punct")
|
|
13
|
+
return OPENERS.has(token.text);
|
|
14
|
+
return /^[\p{Lu}\p{N}]/u.test(token.text);
|
|
15
|
+
}
|
|
16
|
+
/** True when the period at `index` comes after an abbreviation, so it does not end the sentence. */
|
|
17
|
+
function isAbbreviationPeriod(tokens, index) {
|
|
18
|
+
const previous = tokens[index - 1];
|
|
19
|
+
const token = tokens[index];
|
|
20
|
+
if (token.text !== "." || !isWord(previous) || token.spaceBefore)
|
|
21
|
+
return false;
|
|
22
|
+
if (NO_BREAK_ABBREVIATIONS.has(previous.lower))
|
|
23
|
+
return true;
|
|
24
|
+
// "Ms." is a title. In technical text, "ms" is the unit millisecond, and it can end a sentence.
|
|
25
|
+
if (previous.text === "Ms")
|
|
26
|
+
return true;
|
|
27
|
+
if (NUMBER_ABBREVIATIONS.has(previous.lower) && isNumber(tokens[index + 1]))
|
|
28
|
+
return true;
|
|
29
|
+
// An initial, for example "J. Smith".
|
|
30
|
+
return /^\p{Lu}$/u.test(previous.text) && isCapitalized(tokens[index + 1]);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* This function divides tokens into sentences. A sentence stops at `.`,
|
|
34
|
+
* `!`, `?` or an ellipsis, when white space comes next. After the white
|
|
35
|
+
* space, there must be a capital letter, a digit, a placeholder, or an
|
|
36
|
+
* opening bracket or quote. Closing brackets and quotes after the stop stay
|
|
37
|
+
* in the sentence. A period after an abbreviation, for example `e.g.`, does
|
|
38
|
+
* not stop a sentence.
|
|
39
|
+
*/
|
|
40
|
+
export function splitSentences(tokens) {
|
|
41
|
+
const ranges = [];
|
|
42
|
+
let start = 0;
|
|
43
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
44
|
+
const token = tokens[i];
|
|
45
|
+
if (token.kind !== "punct" || !TERMINALS.has(token.text))
|
|
46
|
+
continue;
|
|
47
|
+
if (isAbbreviationPeriod(tokens, i))
|
|
48
|
+
continue;
|
|
49
|
+
let end = i + 1;
|
|
50
|
+
while (end < tokens.length) {
|
|
51
|
+
const next = tokens[end];
|
|
52
|
+
if (next.kind === "punct" && !next.spaceBefore && (CLOSERS.has(next.text) || TERMINALS.has(next.text)))
|
|
53
|
+
end++;
|
|
54
|
+
else
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const next = tokens[end];
|
|
58
|
+
if (next === undefined || (next.spaceBefore && startsSentence(next))) {
|
|
59
|
+
ranges.push({ from: start, to: end });
|
|
60
|
+
start = end;
|
|
61
|
+
i = end - 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (start < tokens.length)
|
|
65
|
+
ranges.push({ from: start, to: tokens.length });
|
|
66
|
+
return ranges.filter((range) => tokens.slice(range.from, range.to).some(isCounted));
|
|
67
|
+
}
|
|
68
|
+
/** The pairs of matching parentheses that no other pair contains. A parenthesis without a partner is ignored. */
|
|
69
|
+
export function topLevelParentheses(tokens) {
|
|
70
|
+
const stack = [];
|
|
71
|
+
const pairs = [];
|
|
72
|
+
tokens.forEach((token, index) => {
|
|
73
|
+
if (token.kind !== "punct")
|
|
74
|
+
return;
|
|
75
|
+
if (token.text === "(") {
|
|
76
|
+
stack.push(index);
|
|
77
|
+
}
|
|
78
|
+
else if (token.text === ")" && stack.length > 0) {
|
|
79
|
+
const open = stack.pop();
|
|
80
|
+
if (stack.length === 0)
|
|
81
|
+
pairs.push({ from: open, to: index + 1 });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return pairs;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* This function counts the words of a sentence as STE Rules 8.5 to 8.7
|
|
88
|
+
* tell:
|
|
89
|
+
*
|
|
90
|
+
* - A placeholder (code span, link tag, URL, quoted text) is one word.
|
|
91
|
+
* - A hyphenated word is one word.
|
|
92
|
+
* - A number and its unit, for example `20 ms`, are one word.
|
|
93
|
+
* - A multi-word proper noun from the glossary is one word.
|
|
94
|
+
* - Two or more words with capital letters after the start of the
|
|
95
|
+
* sentence are a name or a title. They count as one word.
|
|
96
|
+
* - Text in parentheses is one word. Its words make a separate sentence.
|
|
97
|
+
*/
|
|
98
|
+
export function countWords(tokens, options) {
|
|
99
|
+
const parentheses = topLevelParentheses(tokens);
|
|
100
|
+
const parentheticals = [];
|
|
101
|
+
let words = 0;
|
|
102
|
+
let first = true;
|
|
103
|
+
let i = 0;
|
|
104
|
+
while (i < tokens.length) {
|
|
105
|
+
const group = parentheses.find((pair) => pair.from === i);
|
|
106
|
+
if (group !== undefined) {
|
|
107
|
+
const inner = tokens.slice(group.from + 1, group.to - 1);
|
|
108
|
+
if (inner.some(isCounted)) {
|
|
109
|
+
words++;
|
|
110
|
+
parentheticals.push(inner);
|
|
111
|
+
first = false;
|
|
112
|
+
}
|
|
113
|
+
i = group.to;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const token = tokens[i];
|
|
117
|
+
if (!isCounted(token)) {
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
words++;
|
|
122
|
+
const phrase = options.properNouns.longestMatch(tokens, i);
|
|
123
|
+
if (phrase > 1) {
|
|
124
|
+
i += phrase;
|
|
125
|
+
}
|
|
126
|
+
else if (isNumber(token) && isWord(tokens[i + 1]) && UNITS.has(tokens[i + 1].lower)) {
|
|
127
|
+
i += 2;
|
|
128
|
+
}
|
|
129
|
+
else if (!first && isCapitalized(token)) {
|
|
130
|
+
i++;
|
|
131
|
+
while (isCapitalized(tokens[i]))
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
i++;
|
|
136
|
+
}
|
|
137
|
+
first = false;
|
|
138
|
+
}
|
|
139
|
+
return { words, parentheticals };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=sentences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentences.js","sourceRoot":"","sources":["../../src/text/sentences.ts"],"names":[],"mappings":"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"}
|