@khanacademy/perseus-linter 0.2.4 → 0.3.0
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +18 -0
- package/dist/es/index.js +277 -407
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +281 -398
- package/dist/index.js.flow +18 -2
- package/dist/index.js.map +1 -1
- package/dist/proptypes.d.ts +9 -0
- package/dist/proptypes.js.flow +17 -0
- package/dist/rule.d.ts +170 -0
- package/dist/rule.js.flow +86 -0
- package/dist/rules/absolute-url.d.ts +3 -0
- package/dist/rules/absolute-url.js.flow +9 -0
- package/dist/rules/all-rules.d.ts +2 -0
- package/dist/rules/all-rules.js.flow +9 -0
- package/dist/rules/blockquoted-math.d.ts +3 -0
- package/dist/rules/blockquoted-math.js.flow +9 -0
- package/dist/rules/blockquoted-widget.d.ts +3 -0
- package/dist/rules/blockquoted-widget.js.flow +9 -0
- package/dist/rules/double-spacing-after-terminal.d.ts +3 -0
- package/dist/rules/double-spacing-after-terminal.js.flow +9 -0
- package/dist/rules/extra-content-spacing.d.ts +3 -0
- package/dist/rules/extra-content-spacing.js.flow +9 -0
- package/dist/rules/heading-level-1.d.ts +3 -0
- package/dist/rules/heading-level-1.js.flow +9 -0
- package/dist/rules/heading-level-skip.d.ts +3 -0
- package/dist/rules/heading-level-skip.js.flow +9 -0
- package/dist/rules/heading-sentence-case.d.ts +3 -0
- package/dist/rules/heading-sentence-case.js.flow +9 -0
- package/dist/rules/heading-title-case.d.ts +3 -0
- package/dist/rules/heading-title-case.js.flow +9 -0
- package/dist/rules/image-alt-text.d.ts +3 -0
- package/dist/rules/image-alt-text.js.flow +9 -0
- package/dist/rules/image-in-table.d.ts +3 -0
- package/dist/rules/image-in-table.js.flow +9 -0
- package/dist/rules/image-spaces-around-urls.d.ts +3 -0
- package/dist/rules/image-spaces-around-urls.js.flow +9 -0
- package/dist/rules/image-widget.d.ts +3 -0
- package/dist/rules/image-widget.js.flow +9 -0
- package/dist/rules/link-click-here.d.ts +3 -0
- package/dist/rules/link-click-here.js.flow +9 -0
- package/dist/rules/lint-utils.d.ts +2 -0
- package/dist/rules/lint-utils.js.flow +8 -0
- package/dist/rules/long-paragraph.d.ts +3 -0
- package/dist/rules/long-paragraph.js.flow +9 -0
- package/dist/rules/math-adjacent.d.ts +3 -0
- package/dist/rules/math-adjacent.js.flow +9 -0
- package/dist/rules/math-align-extra-break.d.ts +3 -0
- package/dist/rules/math-align-extra-break.js.flow +9 -0
- package/dist/rules/math-align-linebreaks.d.ts +3 -0
- package/dist/rules/math-align-linebreaks.js.flow +9 -0
- package/dist/rules/math-empty.d.ts +3 -0
- package/dist/rules/math-empty.js.flow +9 -0
- package/dist/rules/math-font-size.d.ts +3 -0
- package/dist/rules/math-font-size.js.flow +9 -0
- package/dist/rules/math-frac.d.ts +3 -0
- package/dist/rules/math-frac.js.flow +9 -0
- package/dist/rules/math-nested.d.ts +3 -0
- package/dist/rules/math-nested.js.flow +9 -0
- package/dist/rules/math-starts-with-space.d.ts +3 -0
- package/dist/rules/math-starts-with-space.js.flow +9 -0
- package/dist/rules/math-text-empty.d.ts +3 -0
- package/dist/rules/math-text-empty.js.flow +9 -0
- package/dist/rules/math-without-dollars.d.ts +3 -0
- package/dist/rules/math-without-dollars.js.flow +9 -0
- package/dist/rules/nested-lists.d.ts +3 -0
- package/dist/rules/nested-lists.js.flow +9 -0
- package/dist/rules/profanity.d.ts +3 -0
- package/dist/rules/profanity.js.flow +9 -0
- package/dist/rules/table-missing-cells.d.ts +3 -0
- package/dist/rules/table-missing-cells.js.flow +9 -0
- package/dist/rules/unbalanced-code-delimiters.d.ts +3 -0
- package/dist/rules/unbalanced-code-delimiters.js.flow +9 -0
- package/dist/rules/unescaped-dollar.d.ts +3 -0
- package/dist/rules/unescaped-dollar.js.flow +9 -0
- package/dist/rules/widget-in-table.d.ts +3 -0
- package/dist/rules/widget-in-table.js.flow +9 -0
- package/dist/selector.d.ts +108 -0
- package/dist/selector.js.flow +31 -0
- package/dist/tree-transformer.d.ts +205 -0
- package/dist/tree-transformer.js.flow +253 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js.flow +12 -0
- package/package.json +4 -4
- package/src/__tests__/{matcher_test.js → matcher.test.ts} +60 -60
- package/src/__tests__/{rule_test.js → rule.test.ts} +13 -5
- package/src/__tests__/{rules_test.js → rules.test.ts} +99 -39
- package/src/__tests__/{selector-parser_test.js → selector-parser.test.ts} +1 -2
- package/src/__tests__/{tree-transformer_test.js → tree-transformer.test.ts} +39 -41
- package/src/{index.js → index.ts} +21 -23
- package/src/{proptypes.js → proptypes.ts} +4 -14
- package/src/{rule.js → rule.ts} +45 -38
- package/src/rules/{absolute-url.js → absolute-url.ts} +4 -5
- package/src/rules/all-rules.ts +71 -0
- package/src/rules/{blockquoted-math.js → blockquoted-math.ts} +3 -4
- package/src/rules/{blockquoted-widget.js → blockquoted-widget.ts} +3 -4
- package/src/rules/{double-spacing-after-terminal.js → double-spacing-after-terminal.ts} +3 -4
- package/src/rules/{extra-content-spacing.js → extra-content-spacing.ts} +3 -4
- package/src/rules/{heading-level-1.js → heading-level-1.ts} +3 -4
- package/src/rules/{heading-level-skip.js → heading-level-skip.ts} +3 -4
- package/src/rules/{heading-sentence-case.js → heading-sentence-case.ts} +3 -4
- package/src/rules/{heading-title-case.js → heading-title-case.ts} +11 -6
- package/src/rules/{image-alt-text.js → image-alt-text.ts} +3 -4
- package/src/rules/{image-in-table.js → image-in-table.ts} +3 -4
- package/src/rules/{image-spaces-around-urls.js → image-spaces-around-urls.ts} +3 -4
- package/src/rules/{image-widget.js → image-widget.ts} +3 -4
- package/src/rules/{link-click-here.js → link-click-here.ts} +3 -4
- package/src/rules/{lint-utils.js → lint-utils.ts} +1 -2
- package/src/rules/{long-paragraph.js → long-paragraph.ts} +3 -4
- package/src/rules/{math-adjacent.js → math-adjacent.ts} +3 -4
- package/src/rules/{math-align-extra-break.js → math-align-extra-break.ts} +3 -4
- package/src/rules/{math-align-linebreaks.js → math-align-linebreaks.ts} +3 -4
- package/src/rules/{math-empty.js → math-empty.ts} +3 -4
- package/src/rules/{math-font-size.js → math-font-size.ts} +3 -4
- package/src/rules/{math-frac.js → math-frac.ts} +3 -4
- package/src/rules/{math-nested.js → math-nested.ts} +3 -4
- package/src/rules/{math-starts-with-space.js → math-starts-with-space.ts} +3 -4
- package/src/rules/{math-text-empty.js → math-text-empty.ts} +3 -4
- package/src/rules/{math-without-dollars.js → math-without-dollars.ts} +3 -4
- package/src/rules/{nested-lists.js → nested-lists.ts} +3 -4
- package/src/rules/{profanity.js → profanity.ts} +3 -4
- package/src/rules/{table-missing-cells.js → table-missing-cells.ts} +3 -4
- package/src/rules/{unbalanced-code-delimiters.js → unbalanced-code-delimiters.ts} +3 -4
- package/src/rules/{unescaped-dollar.js → unescaped-dollar.ts} +3 -4
- package/src/rules/{widget-in-table.js → widget-in-table.ts} +3 -4
- package/src/{selector.js → selector.ts} +12 -13
- package/src/{tree-transformer.js → tree-transformer.ts} +24 -24
- package/src/types.ts +7 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/rules/all-rules.js +0 -72
- package/src/types.js +0 -10
package/dist/es/index.js
CHANGED
|
@@ -3,17 +3,14 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
|
|
4
4
|
function ownKeys(object, enumerableOnly) {
|
|
5
5
|
var keys = Object.keys(object);
|
|
6
|
-
|
|
7
6
|
if (Object.getOwnPropertySymbols) {
|
|
8
7
|
var symbols = Object.getOwnPropertySymbols(object);
|
|
9
8
|
enumerableOnly && (symbols = symbols.filter(function (sym) {
|
|
10
9
|
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
11
10
|
})), keys.push.apply(keys, symbols);
|
|
12
11
|
}
|
|
13
|
-
|
|
14
12
|
return keys;
|
|
15
13
|
}
|
|
16
|
-
|
|
17
14
|
function _objectSpread2(target) {
|
|
18
15
|
for (var i = 1; i < arguments.length; i++) {
|
|
19
16
|
var source = null != arguments[i] ? arguments[i] : {};
|
|
@@ -23,11 +20,10 @@ function _objectSpread2(target) {
|
|
|
23
20
|
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
24
21
|
});
|
|
25
22
|
}
|
|
26
|
-
|
|
27
23
|
return target;
|
|
28
24
|
}
|
|
29
|
-
|
|
30
25
|
function _defineProperty(obj, key, value) {
|
|
26
|
+
key = _toPropertyKey(key);
|
|
31
27
|
if (key in obj) {
|
|
32
28
|
Object.defineProperty(obj, key, {
|
|
33
29
|
value: value,
|
|
@@ -38,9 +34,22 @@ function _defineProperty(obj, key, value) {
|
|
|
38
34
|
} else {
|
|
39
35
|
obj[key] = value;
|
|
40
36
|
}
|
|
41
|
-
|
|
42
37
|
return obj;
|
|
43
38
|
}
|
|
39
|
+
function _toPrimitive(input, hint) {
|
|
40
|
+
if (typeof input !== "object" || input === null) return input;
|
|
41
|
+
var prim = input[Symbol.toPrimitive];
|
|
42
|
+
if (prim !== undefined) {
|
|
43
|
+
var res = prim.call(input, hint || "default");
|
|
44
|
+
if (typeof res !== "object") return res;
|
|
45
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
46
|
+
}
|
|
47
|
+
return (hint === "string" ? String : Number)(input);
|
|
48
|
+
}
|
|
49
|
+
function _toPropertyKey(arg) {
|
|
50
|
+
var key = _toPrimitive(arg, "string");
|
|
51
|
+
return typeof key === "symbol" ? key : String(key);
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
46
55
|
* This is the base class for all Selector types. The key method that all
|
|
@@ -53,27 +62,25 @@ class Selector {
|
|
|
53
62
|
static parse(selectorText) {
|
|
54
63
|
return new Parser(selectorText).parse();
|
|
55
64
|
}
|
|
65
|
+
|
|
56
66
|
/**
|
|
57
67
|
* Return an array of the nodes that matched or null if no match.
|
|
58
68
|
* This is the base class so we just throw an exception. All Selector
|
|
59
69
|
* subclasses must provide an implementation of this method.
|
|
60
70
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
71
|
match(state) {
|
|
64
72
|
throw new PerseusError("Selector subclasses must implement match()", Errors.NotAllowed);
|
|
65
73
|
}
|
|
74
|
+
|
|
66
75
|
/**
|
|
67
76
|
* Selector subclasses all define a toString() method primarily
|
|
68
77
|
* because it makes it easy to write parser tests.
|
|
69
78
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
79
|
toString() {
|
|
73
80
|
return "Unknown selector class";
|
|
74
81
|
}
|
|
75
|
-
|
|
76
82
|
}
|
|
83
|
+
|
|
77
84
|
/**
|
|
78
85
|
* This class implements a parser for the selector grammar. Pass the source
|
|
79
86
|
* text to the Parser() constructor, and then call the parse() method to
|
|
@@ -83,98 +90,96 @@ class Selector {
|
|
|
83
90
|
* This class is not exported, and you don't need to use it directly.
|
|
84
91
|
* Instead call the static Selector.parse() method.
|
|
85
92
|
*/
|
|
86
|
-
|
|
87
93
|
class Parser {
|
|
88
94
|
// We do lexing with a simple regular expression
|
|
89
95
|
// The array of tokens
|
|
90
96
|
// Which token in the array we're looking at now
|
|
97
|
+
|
|
91
98
|
constructor(s) {
|
|
92
99
|
_defineProperty(this, "tokens", void 0);
|
|
93
|
-
|
|
94
100
|
_defineProperty(this, "tokenIndex", void 0);
|
|
95
|
-
|
|
96
101
|
// Normalize whitespace:
|
|
97
102
|
// - remove leading and trailing whitespace
|
|
98
103
|
// - replace runs of whitespace with single space characters
|
|
99
|
-
s = s.trim().replace(/\s+/g, " ");
|
|
104
|
+
s = s.trim().replace(/\s+/g, " ");
|
|
105
|
+
// Convert the string to an array of tokens. Note that the TOKENS
|
|
100
106
|
// pattern ignores spaces that do not appear before identifiers
|
|
101
107
|
// or the * wildcard.
|
|
102
|
-
|
|
103
108
|
this.tokens = s.match(Parser.TOKENS) || [];
|
|
104
109
|
this.tokenIndex = 0;
|
|
105
|
-
}
|
|
106
|
-
|
|
110
|
+
}
|
|
107
111
|
|
|
112
|
+
// Return the next token or the empty string if there are no more
|
|
108
113
|
nextToken() {
|
|
109
114
|
return this.tokens[this.tokenIndex] || "";
|
|
110
|
-
}
|
|
111
|
-
// and move on to the next one.
|
|
112
|
-
|
|
115
|
+
}
|
|
113
116
|
|
|
117
|
+
// Increment the token index to "consume" the token we were looking at
|
|
118
|
+
// and move on to the next one.
|
|
114
119
|
consume() {
|
|
115
120
|
this.tokenIndex++;
|
|
116
|
-
}
|
|
117
|
-
|
|
121
|
+
}
|
|
118
122
|
|
|
123
|
+
// Return true if the current token is an identifier or false otherwise
|
|
119
124
|
isIdentifier() {
|
|
120
125
|
// The Parser.TOKENS regexp ensures that we only have to check
|
|
121
126
|
// the first character of a token to know what kind of token it is.
|
|
122
127
|
var c = this.tokens[this.tokenIndex][0];
|
|
123
128
|
return c >= "a" && c <= "z" || c >= "A" && c <= "Z";
|
|
124
|
-
}
|
|
125
|
-
|
|
129
|
+
}
|
|
126
130
|
|
|
131
|
+
// Consume space tokens until the next token is not a space.
|
|
127
132
|
skipSpace() {
|
|
128
133
|
while (this.nextToken() === " ") {
|
|
129
134
|
this.consume();
|
|
130
135
|
}
|
|
131
|
-
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Parse a comma-separated sequence of tree selectors. This is the
|
|
132
139
|
// entry point for the Parser class and the only method that clients
|
|
133
140
|
// ever need to call.
|
|
134
|
-
|
|
135
|
-
|
|
136
141
|
parse() {
|
|
137
142
|
// We expect at least one tree selector
|
|
138
|
-
var ts = this.parseTreeSelector();
|
|
143
|
+
var ts = this.parseTreeSelector();
|
|
139
144
|
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
// Now see what's next
|
|
146
|
+
var token = this.nextToken();
|
|
142
147
|
|
|
148
|
+
// If there is no next token then we're done parsing and can return
|
|
149
|
+
// the tree selector object we got above
|
|
143
150
|
if (!token) {
|
|
144
151
|
return ts;
|
|
145
|
-
}
|
|
146
|
-
// list of tree selectors
|
|
147
|
-
|
|
152
|
+
}
|
|
148
153
|
|
|
154
|
+
// Otherwise, there is more go come and we're going to need a
|
|
155
|
+
// list of tree selectors
|
|
149
156
|
var treeSelectors = [ts];
|
|
150
|
-
|
|
151
157
|
while (token) {
|
|
152
158
|
// The only character we allow after a tree selector is a comma
|
|
153
159
|
if (token === ",") {
|
|
154
160
|
this.consume();
|
|
155
161
|
} else {
|
|
156
162
|
throw new ParseError("Expected comma");
|
|
157
|
-
}
|
|
158
|
-
// tree selector
|
|
159
|
-
|
|
163
|
+
}
|
|
160
164
|
|
|
165
|
+
// And if we saw a comma, then it must be followed by another
|
|
166
|
+
// tree selector
|
|
161
167
|
treeSelectors.push(this.parseTreeSelector());
|
|
162
168
|
token = this.nextToken();
|
|
163
|
-
}
|
|
164
|
-
// SelectorList object.
|
|
165
|
-
|
|
169
|
+
}
|
|
166
170
|
|
|
171
|
+
// If we parsed more than one tree selector, return them in a
|
|
172
|
+
// SelectorList object.
|
|
167
173
|
return new SelectorList(treeSelectors);
|
|
168
|
-
}
|
|
169
|
-
// hierarchy combinators: space, >, + and ~.
|
|
170
|
-
|
|
174
|
+
}
|
|
171
175
|
|
|
176
|
+
// Parse a sequence of node selectors linked together with
|
|
177
|
+
// hierarchy combinators: space, >, + and ~.
|
|
172
178
|
parseTreeSelector() {
|
|
173
179
|
this.skipSpace(); // Ignore space after a comma, for example
|
|
174
|
-
// A tree selector must begin with a node selector
|
|
175
180
|
|
|
181
|
+
// A tree selector must begin with a node selector
|
|
176
182
|
var ns = this.parseNodeSelector();
|
|
177
|
-
|
|
178
183
|
for (;;) {
|
|
179
184
|
// Now check the next token. If there is none, or if it is a
|
|
180
185
|
// comma, then we're done with the treeSelector. Otherwise
|
|
@@ -184,7 +189,6 @@ class Parser {
|
|
|
184
189
|
// combine the current node selector with the new node selector
|
|
185
190
|
// using a Selector subclass that depends on the combinator.
|
|
186
191
|
var token = this.nextToken();
|
|
187
|
-
|
|
188
192
|
if (!token || token === ",") {
|
|
189
193
|
break;
|
|
190
194
|
} else if (token === " ") {
|
|
@@ -203,303 +207,240 @@ class Parser {
|
|
|
203
207
|
throw new ParseError("Unexpected token: " + token);
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
|
-
|
|
207
210
|
return ns;
|
|
208
|
-
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Parse a single node selector.
|
|
209
214
|
// For now, this is just a node type or a wildcard.
|
|
210
215
|
//
|
|
211
216
|
// TODO(davidflanagan): we may need to extend this with attribute
|
|
212
217
|
// selectors like 'heading[level=3]', or with pseudo-classes like
|
|
213
218
|
// paragraph:first-child
|
|
214
|
-
|
|
215
|
-
|
|
216
219
|
parseNodeSelector() {
|
|
217
220
|
// First, skip any whitespace
|
|
218
221
|
this.skipSpace();
|
|
219
222
|
var t = this.nextToken();
|
|
220
|
-
|
|
221
223
|
if (t === "*") {
|
|
222
224
|
this.consume();
|
|
223
225
|
return new AnyNode();
|
|
224
226
|
}
|
|
225
|
-
|
|
226
227
|
if (this.isIdentifier()) {
|
|
227
228
|
this.consume();
|
|
228
229
|
return new TypeSelector(t);
|
|
229
230
|
}
|
|
230
|
-
|
|
231
231
|
throw new ParseError("Expected node type");
|
|
232
232
|
}
|
|
233
|
+
}
|
|
233
234
|
|
|
234
|
-
|
|
235
|
+
// We break the input string into tokens with this regexp. Token types
|
|
235
236
|
// are identifiers, integers, punctuation and spaces. Note that spaces
|
|
236
237
|
// tokens are only returned when they appear before an identifier or
|
|
237
238
|
// wildcard token and are otherwise omitted.
|
|
238
|
-
|
|
239
|
-
|
|
240
239
|
_defineProperty(Parser, "TOKENS", void 0);
|
|
241
|
-
|
|
242
240
|
Parser.TOKENS = /([a-zA-Z][\w-]*)|(\d+)|[^\s]|(\s(?=[a-zA-Z\*]))/g;
|
|
241
|
+
|
|
243
242
|
/**
|
|
244
243
|
* This is a trivial Error subclass that the Parser uses to signal parse errors
|
|
245
244
|
*/
|
|
246
|
-
|
|
247
245
|
class ParseError extends Error {
|
|
248
246
|
constructor(message) {
|
|
249
247
|
super(message);
|
|
250
248
|
}
|
|
251
|
-
|
|
252
249
|
}
|
|
250
|
+
|
|
253
251
|
/**
|
|
254
252
|
* This Selector subclass is a list of selectors. It matches a node if any of
|
|
255
253
|
* the selectors on the list matches the node. It considers the selectors in
|
|
256
254
|
* order, and returns the array of nodes returned by whichever one matches
|
|
257
255
|
* first.
|
|
258
256
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
257
|
class SelectorList extends Selector {
|
|
262
258
|
constructor(selectors) {
|
|
263
259
|
super();
|
|
264
|
-
|
|
265
260
|
_defineProperty(this, "selectors", void 0);
|
|
266
|
-
|
|
267
261
|
this.selectors = selectors;
|
|
268
262
|
}
|
|
269
|
-
|
|
270
263
|
match(state) {
|
|
271
264
|
for (var i = 0; i < this.selectors.length; i++) {
|
|
272
265
|
var s = this.selectors[i];
|
|
273
266
|
var result = s.match(state);
|
|
274
|
-
|
|
275
267
|
if (result) {
|
|
276
268
|
return result;
|
|
277
269
|
}
|
|
278
270
|
}
|
|
279
|
-
|
|
280
271
|
return null;
|
|
281
272
|
}
|
|
282
|
-
|
|
283
273
|
toString() {
|
|
284
274
|
var result = "";
|
|
285
|
-
|
|
286
275
|
for (var i = 0; i < this.selectors.length; i++) {
|
|
287
276
|
result += i > 0 ? ", " : "";
|
|
288
277
|
result += this.selectors[i].toString();
|
|
289
278
|
}
|
|
290
|
-
|
|
291
279
|
return result;
|
|
292
280
|
}
|
|
293
|
-
|
|
294
281
|
}
|
|
282
|
+
|
|
295
283
|
/**
|
|
296
284
|
* This trivial Selector subclass implements the '*' wildcard and
|
|
297
285
|
* matches any node.
|
|
298
286
|
*/
|
|
299
|
-
|
|
300
|
-
|
|
301
287
|
class AnyNode extends Selector {
|
|
302
288
|
match(state) {
|
|
303
289
|
return [state.currentNode()];
|
|
304
290
|
}
|
|
305
|
-
|
|
306
291
|
toString() {
|
|
307
292
|
return "*";
|
|
308
293
|
}
|
|
309
|
-
|
|
310
294
|
}
|
|
295
|
+
|
|
311
296
|
/**
|
|
312
297
|
* This selector subclass implements the <IDENTIFIER> part of the grammar.
|
|
313
298
|
* it matches any node whose `type` property is a specified string
|
|
314
299
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
317
300
|
class TypeSelector extends Selector {
|
|
318
301
|
constructor(type) {
|
|
319
302
|
super();
|
|
320
|
-
|
|
321
303
|
_defineProperty(this, "type", void 0);
|
|
322
|
-
|
|
323
304
|
this.type = type;
|
|
324
305
|
}
|
|
325
|
-
|
|
326
306
|
match(state) {
|
|
327
307
|
var node = state.currentNode();
|
|
328
|
-
|
|
329
308
|
if (node.type === this.type) {
|
|
330
309
|
return [node];
|
|
331
310
|
}
|
|
332
|
-
|
|
333
311
|
return null;
|
|
334
312
|
}
|
|
335
|
-
|
|
336
313
|
toString() {
|
|
337
314
|
return this.type;
|
|
338
315
|
}
|
|
339
|
-
|
|
340
316
|
}
|
|
317
|
+
|
|
341
318
|
/**
|
|
342
319
|
* This selector subclass is the superclass of the classes that implement
|
|
343
320
|
* matching for the four combinators. It defines left and right properties for
|
|
344
321
|
* the two selectors that are to be combined, but does not define a match
|
|
345
322
|
* method.
|
|
346
323
|
*/
|
|
347
|
-
|
|
348
|
-
|
|
349
324
|
class SelectorCombinator extends Selector {
|
|
350
325
|
constructor(left, right) {
|
|
351
326
|
super();
|
|
352
|
-
|
|
353
327
|
_defineProperty(this, "left", void 0);
|
|
354
|
-
|
|
355
328
|
_defineProperty(this, "right", void 0);
|
|
356
|
-
|
|
357
329
|
this.left = left;
|
|
358
330
|
this.right = right;
|
|
359
331
|
}
|
|
360
|
-
|
|
361
332
|
}
|
|
333
|
+
|
|
362
334
|
/**
|
|
363
335
|
* This Selector subclass implements the space combinator. It matches if the
|
|
364
336
|
* right selector matches the current node and the left selector matches some
|
|
365
337
|
* ancestor of the current node.
|
|
366
338
|
*/
|
|
367
|
-
|
|
368
|
-
|
|
369
339
|
class AncestorCombinator extends SelectorCombinator {
|
|
370
340
|
constructor(left, right) {
|
|
371
341
|
super(left, right);
|
|
372
342
|
}
|
|
373
|
-
|
|
374
343
|
match(state) {
|
|
375
344
|
var rightResult = this.right.match(state);
|
|
376
|
-
|
|
377
345
|
if (rightResult) {
|
|
378
346
|
state = state.clone();
|
|
379
|
-
|
|
380
347
|
while (state.hasParent()) {
|
|
381
348
|
state.goToParent();
|
|
382
349
|
var leftResult = this.left.match(state);
|
|
383
|
-
|
|
384
350
|
if (leftResult) {
|
|
385
351
|
return leftResult.concat(rightResult);
|
|
386
352
|
}
|
|
387
353
|
}
|
|
388
354
|
}
|
|
389
|
-
|
|
390
355
|
return null;
|
|
391
356
|
}
|
|
392
|
-
|
|
393
357
|
toString() {
|
|
394
358
|
return this.left.toString() + " " + this.right.toString();
|
|
395
359
|
}
|
|
396
|
-
|
|
397
360
|
}
|
|
361
|
+
|
|
398
362
|
/**
|
|
399
363
|
* This Selector subclass implements the > combinator. It matches if the
|
|
400
364
|
* right selector matches the current node and the left selector matches
|
|
401
365
|
* the parent of the current node.
|
|
402
366
|
*/
|
|
403
|
-
|
|
404
|
-
|
|
405
367
|
class ParentCombinator extends SelectorCombinator {
|
|
406
368
|
constructor(left, right) {
|
|
407
369
|
super(left, right);
|
|
408
370
|
}
|
|
409
|
-
|
|
410
371
|
match(state) {
|
|
411
372
|
var rightResult = this.right.match(state);
|
|
412
|
-
|
|
413
373
|
if (rightResult) {
|
|
414
374
|
if (state.hasParent()) {
|
|
415
375
|
state = state.clone();
|
|
416
376
|
state.goToParent();
|
|
417
377
|
var leftResult = this.left.match(state);
|
|
418
|
-
|
|
419
378
|
if (leftResult) {
|
|
420
379
|
return leftResult.concat(rightResult);
|
|
421
380
|
}
|
|
422
381
|
}
|
|
423
382
|
}
|
|
424
|
-
|
|
425
383
|
return null;
|
|
426
384
|
}
|
|
427
|
-
|
|
428
385
|
toString() {
|
|
429
386
|
return this.left.toString() + " > " + this.right.toString();
|
|
430
387
|
}
|
|
431
|
-
|
|
432
388
|
}
|
|
389
|
+
|
|
433
390
|
/**
|
|
434
391
|
* This Selector subclass implements the + combinator. It matches if the
|
|
435
392
|
* right selector matches the current node and the left selector matches
|
|
436
393
|
* the immediate previous sibling of the current node.
|
|
437
394
|
*/
|
|
438
|
-
|
|
439
|
-
|
|
440
395
|
class PreviousCombinator extends SelectorCombinator {
|
|
441
396
|
constructor(left, right) {
|
|
442
397
|
super(left, right);
|
|
443
398
|
}
|
|
444
|
-
|
|
445
399
|
match(state) {
|
|
446
400
|
var rightResult = this.right.match(state);
|
|
447
|
-
|
|
448
401
|
if (rightResult) {
|
|
449
402
|
if (state.hasPreviousSibling()) {
|
|
450
403
|
state = state.clone();
|
|
451
404
|
state.goToPreviousSibling();
|
|
452
405
|
var leftResult = this.left.match(state);
|
|
453
|
-
|
|
454
406
|
if (leftResult) {
|
|
455
407
|
return leftResult.concat(rightResult);
|
|
456
408
|
}
|
|
457
409
|
}
|
|
458
410
|
}
|
|
459
|
-
|
|
460
411
|
return null;
|
|
461
412
|
}
|
|
462
|
-
|
|
463
413
|
toString() {
|
|
464
414
|
return this.left.toString() + " + " + this.right.toString();
|
|
465
415
|
}
|
|
466
|
-
|
|
467
416
|
}
|
|
417
|
+
|
|
468
418
|
/**
|
|
469
419
|
* This Selector subclass implements the ~ combinator. It matches if the
|
|
470
420
|
* right selector matches the current node and the left selector matches
|
|
471
421
|
* any previous sibling of the current node.
|
|
472
422
|
*/
|
|
473
|
-
|
|
474
|
-
|
|
475
423
|
class SiblingCombinator extends SelectorCombinator {
|
|
476
424
|
constructor(left, right) {
|
|
477
425
|
super(left, right);
|
|
478
426
|
}
|
|
479
|
-
|
|
480
427
|
match(state) {
|
|
481
428
|
var rightResult = this.right.match(state);
|
|
482
|
-
|
|
483
429
|
if (rightResult) {
|
|
484
430
|
state = state.clone();
|
|
485
|
-
|
|
486
431
|
while (state.hasPreviousSibling()) {
|
|
487
432
|
state.goToPreviousSibling();
|
|
488
433
|
var leftResult = this.left.match(state);
|
|
489
|
-
|
|
490
434
|
if (leftResult) {
|
|
491
435
|
return leftResult.concat(rightResult);
|
|
492
436
|
}
|
|
493
437
|
}
|
|
494
438
|
}
|
|
495
|
-
|
|
496
439
|
return null;
|
|
497
440
|
}
|
|
498
|
-
|
|
499
441
|
toString() {
|
|
500
442
|
return this.left.toString() + " ~ " + this.right.toString();
|
|
501
443
|
}
|
|
502
|
-
|
|
503
444
|
}
|
|
504
445
|
|
|
505
446
|
/**
|
|
@@ -514,25 +455,18 @@ class Rule {
|
|
|
514
455
|
// The lint-testing function or a default
|
|
515
456
|
// Checks to see if we should apply a rule or not
|
|
516
457
|
// The error message for use with the default function
|
|
458
|
+
|
|
517
459
|
// The comment at the top of this file has detailed docs for
|
|
518
460
|
// this constructor and its arguments
|
|
519
461
|
constructor(name, severity, selector, pattern, lint, applies) {
|
|
520
462
|
var _this = this;
|
|
521
|
-
|
|
522
463
|
_defineProperty(this, "name", void 0);
|
|
523
|
-
|
|
524
464
|
_defineProperty(this, "severity", void 0);
|
|
525
|
-
|
|
526
465
|
_defineProperty(this, "selector", void 0);
|
|
527
|
-
|
|
528
466
|
_defineProperty(this, "pattern", void 0);
|
|
529
|
-
|
|
530
467
|
_defineProperty(this, "lint", void 0);
|
|
531
|
-
|
|
532
468
|
_defineProperty(this, "applies", void 0);
|
|
533
|
-
|
|
534
469
|
_defineProperty(this, "message", void 0);
|
|
535
|
-
|
|
536
470
|
if (!selector && !pattern) {
|
|
537
471
|
throw new PerseusError("Lint rules must have a selector or pattern", Errors.InvalidInput, {
|
|
538
472
|
metadata: {
|
|
@@ -540,13 +474,13 @@ class Rule {
|
|
|
540
474
|
}
|
|
541
475
|
});
|
|
542
476
|
}
|
|
543
|
-
|
|
544
477
|
this.name = name || "unnamed rule";
|
|
545
478
|
this.severity = severity || Rule.Severity.BULK_WARNING;
|
|
546
479
|
this.selector = selector || Rule.DEFAULT_SELECTOR;
|
|
547
|
-
this.pattern = pattern || null;
|
|
548
|
-
// use a default function that will return the message.
|
|
480
|
+
this.pattern = pattern || null;
|
|
549
481
|
|
|
482
|
+
// If we're called with an error message instead of a function then
|
|
483
|
+
// use a default function that will return the message.
|
|
550
484
|
if (typeof lint === "function") {
|
|
551
485
|
this.lint = lint;
|
|
552
486
|
this.message = null;
|
|
@@ -554,55 +488,51 @@ class Rule {
|
|
|
554
488
|
this.lint = function () {
|
|
555
489
|
return _this._defaultLintFunction(...arguments);
|
|
556
490
|
};
|
|
557
|
-
|
|
558
491
|
this.message = lint;
|
|
559
492
|
}
|
|
560
|
-
|
|
561
493
|
this.applies = applies || function () {
|
|
562
494
|
return true;
|
|
563
495
|
};
|
|
564
|
-
}
|
|
565
|
-
// See the documentation at the start of this file for details.
|
|
566
|
-
|
|
496
|
+
}
|
|
567
497
|
|
|
498
|
+
// A factory method for use with rules described in JSON files
|
|
499
|
+
// See the documentation at the start of this file for details.
|
|
568
500
|
static makeRule(options) {
|
|
569
501
|
return new Rule(options.name, options.severity, options.selector ? Selector.parse(options.selector) : null, Rule.makePattern(options.pattern), options.lint || options.message, options.applies);
|
|
570
|
-
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Check the node n to see if it violates this lint rule. A return value
|
|
571
505
|
// of false means there is no lint. A returned object indicates a lint
|
|
572
506
|
// error. See the documentation at the top of this file for details.
|
|
573
|
-
|
|
574
|
-
|
|
575
507
|
check(node, traversalState, content, context) {
|
|
576
508
|
// First, see if we match the selector.
|
|
577
509
|
// If no selector was passed to the constructor, we use a
|
|
578
510
|
// default selector that matches text nodes.
|
|
579
|
-
var selectorMatch = this.selector.match(traversalState);
|
|
511
|
+
var selectorMatch = this.selector.match(traversalState);
|
|
580
512
|
|
|
513
|
+
// If the selector did not match, then we're done
|
|
581
514
|
if (!selectorMatch) {
|
|
582
515
|
return null;
|
|
583
|
-
}
|
|
584
|
-
|
|
516
|
+
}
|
|
585
517
|
|
|
518
|
+
// If the selector matched, then see if the pattern matches
|
|
586
519
|
var patternMatch;
|
|
587
|
-
|
|
588
520
|
if (this.pattern) {
|
|
589
521
|
patternMatch = content.match(this.pattern);
|
|
590
522
|
} else {
|
|
591
523
|
// If there is no pattern, then just match all of the content.
|
|
592
524
|
// Use a fake RegExp match object to represent this default match.
|
|
593
525
|
patternMatch = Rule.FakePatternMatch(content, content, 0);
|
|
594
|
-
}
|
|
595
|
-
|
|
526
|
+
}
|
|
596
527
|
|
|
528
|
+
// If there was a pattern and it didn't match, then we're done
|
|
597
529
|
if (!patternMatch) {
|
|
598
530
|
return null;
|
|
599
531
|
}
|
|
600
|
-
|
|
601
532
|
try {
|
|
602
533
|
// If we get here, then the selector and pattern have matched
|
|
603
534
|
// so now we call the lint function to see if there is lint.
|
|
604
535
|
var error = this.lint(traversalState, content, selectorMatch, patternMatch, context);
|
|
605
|
-
|
|
606
536
|
if (!error) {
|
|
607
537
|
return null; // No lint; we're done
|
|
608
538
|
}
|
|
@@ -617,10 +547,9 @@ class Rule {
|
|
|
617
547
|
start: 0,
|
|
618
548
|
end: content.length
|
|
619
549
|
};
|
|
620
|
-
}
|
|
550
|
+
}
|
|
551
|
+
// If the lint function returned an object, then we just
|
|
621
552
|
// add the rule name to the message, start and end.
|
|
622
|
-
|
|
623
|
-
|
|
624
553
|
return {
|
|
625
554
|
rule: this.name,
|
|
626
555
|
severity: this.severity,
|
|
@@ -641,22 +570,24 @@ class Rule {
|
|
|
641
570
|
end: content.length
|
|
642
571
|
};
|
|
643
572
|
}
|
|
644
|
-
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// This internal method is the default lint function that we use when a
|
|
645
576
|
// rule is defined without a function. This is useful for rules where the
|
|
646
577
|
// selector and/or pattern match are enough to indicate lint. This
|
|
647
578
|
// function unconditionally returns the error message that was passed in
|
|
648
579
|
// place of a function, but also adds start and end properties that
|
|
649
580
|
// specify which particular portion of the node content matched the
|
|
650
581
|
// pattern.
|
|
651
|
-
|
|
652
|
-
|
|
653
582
|
_defaultLintFunction(state, content, selectorMatch, patternMatch, context) {
|
|
654
583
|
return {
|
|
655
584
|
message: this.message || "",
|
|
656
585
|
start: patternMatch.index,
|
|
657
586
|
end: patternMatch.index + patternMatch[0].length
|
|
658
587
|
};
|
|
659
|
-
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// The makeRule() factory function uses this static method to turn its
|
|
660
591
|
// argument into a RegExp. If the argument is already a RegExp, we just
|
|
661
592
|
// return it. Otherwise, we compile it into a RegExp and return that.
|
|
662
593
|
// The reason this is necessary is that Rule.makeRule() is designed for
|
|
@@ -671,49 +602,41 @@ class Rule {
|
|
|
671
602
|
// input "foo" ==> output /foo/
|
|
672
603
|
// input "/foo/i" ==> output /foo/i
|
|
673
604
|
//
|
|
674
|
-
|
|
675
|
-
|
|
676
605
|
static makePattern(pattern) {
|
|
677
606
|
if (!pattern) {
|
|
678
607
|
return null;
|
|
679
608
|
}
|
|
680
|
-
|
|
681
609
|
if (pattern instanceof RegExp) {
|
|
682
610
|
return pattern;
|
|
683
611
|
}
|
|
684
|
-
|
|
685
612
|
if (pattern[0] === "/") {
|
|
686
613
|
var lastSlash = pattern.lastIndexOf("/");
|
|
687
614
|
var expression = pattern.substring(1, lastSlash);
|
|
688
615
|
var flags = pattern.substring(lastSlash + 1);
|
|
616
|
+
// @ts-expect-error [FEI-5003] - TS2713 - Cannot access 'RegExp.flags' because 'RegExp' is a type, but not a namespace. Did you mean to retrieve the type of the property 'flags' in 'RegExp' with 'RegExp["flags"]'?
|
|
689
617
|
return new RegExp(expression, flags);
|
|
690
618
|
}
|
|
691
|
-
|
|
692
619
|
return new RegExp(pattern);
|
|
693
|
-
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// This static method returns an string array with index and input
|
|
694
623
|
// properties added, in order to simulate the return value of the
|
|
695
624
|
// String.match() method. We use it when a Rule has no pattern and we
|
|
696
625
|
// want to simulate a match on the entire content string.
|
|
697
|
-
|
|
698
|
-
|
|
699
626
|
static FakePatternMatch(input, match, index) {
|
|
700
627
|
var result = [match];
|
|
701
628
|
result.index = index;
|
|
702
629
|
result.input = input;
|
|
703
630
|
return result;
|
|
704
631
|
}
|
|
705
|
-
|
|
706
632
|
}
|
|
707
|
-
|
|
708
633
|
_defineProperty(Rule, "DEFAULT_SELECTOR", void 0);
|
|
709
|
-
|
|
710
634
|
_defineProperty(Rule, "Severity", {
|
|
711
635
|
ERROR: 1,
|
|
712
636
|
WARNING: 2,
|
|
713
637
|
GUIDELINE: 3,
|
|
714
638
|
BULK_WARNING: 4
|
|
715
639
|
});
|
|
716
|
-
|
|
717
640
|
Rule.DEFAULT_SELECTOR = Selector.parse("text");
|
|
718
641
|
|
|
719
642
|
/* eslint-disable no-useless-escape */
|
|
@@ -721,17 +644,17 @@ Rule.DEFAULT_SELECTOR = Selector.parse("text");
|
|
|
721
644
|
// portion which is usually just the hostname, but may also include
|
|
722
645
|
// a username, password or port. We don't strip those things out because
|
|
723
646
|
// we typically want to reject any URL that includes them
|
|
724
|
-
var HOSTNAME = /\/\/([^\/]+)/;
|
|
725
|
-
// If this is a relative URL with no hostname, return an empty string.
|
|
647
|
+
var HOSTNAME = /\/\/([^\/]+)/;
|
|
726
648
|
|
|
649
|
+
// Return the hostname of the URL, with any "www." prefix removed.
|
|
650
|
+
// If this is a relative URL with no hostname, return an empty string.
|
|
727
651
|
function getHostname(url) {
|
|
728
652
|
if (!url) {
|
|
729
653
|
return "";
|
|
730
654
|
}
|
|
731
|
-
|
|
732
655
|
var match = url.match(HOSTNAME);
|
|
733
656
|
return match ? match[1] : "";
|
|
734
|
-
}
|
|
657
|
+
}
|
|
735
658
|
|
|
736
659
|
var AbsoluteUrl = Rule.makeRule({
|
|
737
660
|
name: "absolute-url",
|
|
@@ -740,7 +663,6 @@ var AbsoluteUrl = Rule.makeRule({
|
|
|
740
663
|
lint: function lint(state, content, nodes, match) {
|
|
741
664
|
var url = nodes[0].target;
|
|
742
665
|
var hostname = getHostname(url);
|
|
743
|
-
|
|
744
666
|
if (hostname === "khanacademy.org" || hostname.endsWith(".khanacademy.org")) {
|
|
745
667
|
return "Don't use absolute URLs:\nWhen linking to KA content or images, omit the\nhttps://www.khanacademy.org URL prefix.\nUse a relative URL beginning with / instead.";
|
|
746
668
|
}
|
|
@@ -797,10 +719,10 @@ var HeadingLevelSkip = Rule.makeRule({
|
|
|
797
719
|
selector: "heading ~ heading",
|
|
798
720
|
lint: function lint(state, content, nodes, match) {
|
|
799
721
|
var currentHeading = nodes[1];
|
|
800
|
-
var previousHeading = nodes[0];
|
|
722
|
+
var previousHeading = nodes[0];
|
|
723
|
+
// A heading can have a level less than, the same as
|
|
801
724
|
// or one more than the previous heading. But going up
|
|
802
725
|
// by 2 or more levels is not right
|
|
803
|
-
|
|
804
726
|
if (currentHeading.level > previousHeading.level + 1) {
|
|
805
727
|
return "Skipped heading level:\nthis heading is level ".concat(currentHeading.level, " but\nthe previous heading was level ").concat(previousHeading.level);
|
|
806
728
|
}
|
|
@@ -816,9 +738,9 @@ var HeadingSentenceCase = Rule.makeRule({
|
|
|
816
738
|
message: "First letter is lowercase:\nthe first letter of a heading should be capitalized."
|
|
817
739
|
});
|
|
818
740
|
|
|
741
|
+
// These are 3-letter and longer words that we would not expect to be
|
|
819
742
|
// capitalized even in a title-case heading. See
|
|
820
743
|
// http://blog.apastyle.org/apastyle/2012/03/title-case-and-sentence-case-capitalization-in-apa-style.html
|
|
821
|
-
|
|
822
744
|
var littleWords = {
|
|
823
745
|
and: true,
|
|
824
746
|
nor: true,
|
|
@@ -826,12 +748,10 @@ var littleWords = {
|
|
|
826
748
|
the: true,
|
|
827
749
|
for: true
|
|
828
750
|
};
|
|
829
|
-
|
|
830
751
|
function isCapitalized(word) {
|
|
831
752
|
var c = word[0];
|
|
832
753
|
return c === c.toUpperCase();
|
|
833
754
|
}
|
|
834
|
-
|
|
835
755
|
var HeadingTitleCase = Rule.makeRule({
|
|
836
756
|
name: "heading-title-case",
|
|
837
757
|
severity: Rule.Severity.GUIDELINE,
|
|
@@ -862,14 +782,18 @@ var HeadingTitleCase = Rule.makeRule({
|
|
|
862
782
|
// It is marked with a locale property above, but that is NYI
|
|
863
783
|
//
|
|
864
784
|
// for APA style rules for title case
|
|
785
|
+
|
|
865
786
|
var heading = content.trim();
|
|
866
|
-
var words = heading.split(/\s+/);
|
|
787
|
+
var words = heading.split(/\s+/);
|
|
867
788
|
|
|
789
|
+
// Remove the first word and the little words
|
|
868
790
|
words.shift();
|
|
869
|
-
words = words.filter(
|
|
870
|
-
|
|
871
|
-
|
|
791
|
+
words = words.filter(
|
|
792
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
793
|
+
w => w.length > 2 && !littleWords.hasOwnProperty(w));
|
|
872
794
|
|
|
795
|
+
// If there are at least 3 remaining words and all
|
|
796
|
+
// are capitalized, then the heading is in title case.
|
|
873
797
|
if (words.length >= 3 && words.every(w => isCapitalized(w))) {
|
|
874
798
|
return "Title-case heading:\nThis heading appears to be in title-case, but should be sentence-case.\nOnly capitalize the first letter and proper nouns.";
|
|
875
799
|
}
|
|
@@ -882,11 +806,9 @@ var ImageAltText = Rule.makeRule({
|
|
|
882
806
|
selector: "image",
|
|
883
807
|
lint: function lint(state, content, nodes, match) {
|
|
884
808
|
var image = nodes[0];
|
|
885
|
-
|
|
886
809
|
if (!image.alt || !image.alt.trim()) {
|
|
887
810
|
return "Images should have alt text:\nfor accessibility, all images should have alt text.\nSpecify alt text inside square brackets after the !.";
|
|
888
811
|
}
|
|
889
|
-
|
|
890
812
|
if (image.alt.length < 8) {
|
|
891
813
|
return "Images should have alt text:\nfor accessibility, all images should have descriptive alt text.\nThis image's alt text is only ".concat(image.alt.length, " characters long.");
|
|
892
814
|
}
|
|
@@ -906,21 +828,20 @@ var ImageSpacesAroundUrls = Rule.makeRule({
|
|
|
906
828
|
selector: "image",
|
|
907
829
|
lint: function lint(state, content, nodes, match, context) {
|
|
908
830
|
var image = nodes[0];
|
|
909
|
-
var url = image.target;
|
|
831
|
+
var url = image.target;
|
|
832
|
+
|
|
833
|
+
// The markdown parser strips leading and trailing spaces for us,
|
|
910
834
|
// but they're still a problem for our translation process, so
|
|
911
835
|
// we need to go check for them in the unparsed source string
|
|
912
836
|
// if we have it.
|
|
913
|
-
|
|
914
837
|
if (context && context.content) {
|
|
915
838
|
// Find the url in the original content and make sure that the
|
|
916
839
|
// character before is '(' and the character after is ')'
|
|
917
840
|
var index = context.content.indexOf(url);
|
|
918
|
-
|
|
919
841
|
if (index === -1) {
|
|
920
842
|
// It is not an error if we didn't find it.
|
|
921
843
|
return;
|
|
922
844
|
}
|
|
923
|
-
|
|
924
845
|
if (context.content[index - 1] !== "(" || context.content[index + url.length] !== ")") {
|
|
925
846
|
return "Whitespace before or after image url:\nFor images, don't include any space or newlines after '(' or before ')'.\nWhitespace in image URLs causes translation difficulties.";
|
|
926
847
|
}
|
|
@@ -928,12 +849,12 @@ var ImageSpacesAroundUrls = Rule.makeRule({
|
|
|
928
849
|
}
|
|
929
850
|
});
|
|
930
851
|
|
|
852
|
+
// Normally we have one rule per file. But since our selector class
|
|
931
853
|
// can't match specific widget types directly, this rule implements
|
|
932
854
|
// a number of image widget related rules in one place. This should
|
|
933
855
|
// slightly increase efficiency, but it means that if there is more
|
|
934
856
|
// than one problem with an image widget, the user will only see one
|
|
935
857
|
// problem at a time.
|
|
936
|
-
|
|
937
858
|
var ImageWidget = Rule.makeRule({
|
|
938
859
|
name: "image-widget",
|
|
939
860
|
severity: Rule.Severity.WARNING,
|
|
@@ -942,28 +863,26 @@ var ImageWidget = Rule.makeRule({
|
|
|
942
863
|
// This rule only looks at image widgets
|
|
943
864
|
if (state.currentNode().widgetType !== "image") {
|
|
944
865
|
return;
|
|
945
|
-
}
|
|
946
|
-
|
|
866
|
+
}
|
|
947
867
|
|
|
868
|
+
// If it can't find a definition for the widget it does nothing
|
|
948
869
|
var widget = context && context.widgets && context.widgets[state.currentNode().id];
|
|
949
|
-
|
|
950
870
|
if (!widget) {
|
|
951
871
|
return;
|
|
952
|
-
}
|
|
953
|
-
|
|
872
|
+
}
|
|
954
873
|
|
|
874
|
+
// Make sure there is alt text
|
|
955
875
|
var alt = widget.options.alt;
|
|
956
|
-
|
|
957
876
|
if (!alt) {
|
|
958
877
|
return "Images should have alt text:\nfor accessibility, all images should have a text description.\nAdd a description in the \"Alt Text\" box of the image widget.";
|
|
959
|
-
}
|
|
960
|
-
|
|
878
|
+
}
|
|
961
879
|
|
|
880
|
+
// Make sure the alt text it is not trivial
|
|
962
881
|
if (alt.trim().length < 8) {
|
|
963
882
|
return "Images should have alt text:\nfor accessibility, all images should have descriptive alt text.\nThis image's alt text is only ".concat(alt.trim().length, " characters long.");
|
|
964
|
-
}
|
|
965
|
-
|
|
883
|
+
}
|
|
966
884
|
|
|
885
|
+
// Make sure there is no math in the caption
|
|
967
886
|
if (widget.options.caption && widget.options.caption.match(/[^\\]\$/)) {
|
|
968
887
|
return "No math in image captions:\nDon't include math expressions in image captions.";
|
|
969
888
|
}
|
|
@@ -1017,29 +936,28 @@ var MathAlignLinebreaks = Rule.makeRule({
|
|
|
1017
936
|
// enforces that you don't have the wrong number of pairs of backslashes.
|
|
1018
937
|
lint: function lint(state, content, nodes, match) {
|
|
1019
938
|
var text = match[0];
|
|
1020
|
-
|
|
1021
939
|
while (text.length) {
|
|
1022
940
|
var index = text.indexOf("\\\\");
|
|
1023
|
-
|
|
1024
941
|
if (index === -1) {
|
|
1025
942
|
// No more backslash pairs, so we found no lint
|
|
1026
943
|
return null;
|
|
1027
944
|
}
|
|
945
|
+
text = text.substring(index + 2);
|
|
1028
946
|
|
|
1029
|
-
|
|
947
|
+
// Now we expect to find optional spaces, another pair of
|
|
1030
948
|
// backslashes, and more optional spaces not followed immediately
|
|
1031
949
|
// by another pair of backslashes.
|
|
950
|
+
var nextpair = text.match(/^\s*\\\\\s*(?!\\\\)/);
|
|
1032
951
|
|
|
1033
|
-
|
|
952
|
+
// If that does not match then we either have too few or too
|
|
1034
953
|
// many pairs of backslashes.
|
|
1035
|
-
|
|
1036
954
|
if (!nextpair) {
|
|
1037
955
|
return "Use four backslashes between lines of an align block";
|
|
1038
|
-
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// If it did match, then, shorten the string and continue looping
|
|
1039
959
|
// (because a single align block may have multiple lines that
|
|
1040
960
|
// all must be separated by two sets of double backslashes).
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
961
|
text = text.substring(nextpair[0].length);
|
|
1044
962
|
}
|
|
1045
963
|
}
|
|
@@ -1093,10 +1011,10 @@ var MathTextEmpty = Rule.makeRule({
|
|
|
1093
1011
|
message: "Empty \\text{} block in math expression"
|
|
1094
1012
|
});
|
|
1095
1013
|
|
|
1014
|
+
// Because no selector is specified, this rule only applies to text nodes.
|
|
1096
1015
|
// Math and code hold their content directly and do not have text nodes
|
|
1097
1016
|
// beneath them (unlike the HTML DOM) so this rule automatically does not
|
|
1098
1017
|
// apply inside $$ or ``.
|
|
1099
|
-
|
|
1100
1018
|
var MathWithoutDollars = Rule.makeRule({
|
|
1101
1019
|
name: "math-without-dollars",
|
|
1102
1020
|
severity: Rule.Severity.GUIDELINE,
|
|
@@ -1127,7 +1045,6 @@ var TableMissingCells = Rule.makeRule({
|
|
|
1127
1045
|
var table = nodes[0];
|
|
1128
1046
|
var headerLength = table.header.length;
|
|
1129
1047
|
var rowLengths = table.cells.map(r => r.length);
|
|
1130
|
-
|
|
1131
1048
|
for (var r = 0; r < rowLengths.length; r++) {
|
|
1132
1049
|
if (rowLengths[r] !== headerLength) {
|
|
1133
1050
|
return "Table rows don't match header:\nThe table header has ".concat(headerLength, " cells, but\nRow ").concat(r + 1, " has ").concat(rowLengths[r], " cells.");
|
|
@@ -1136,10 +1053,10 @@ var TableMissingCells = Rule.makeRule({
|
|
|
1136
1053
|
}
|
|
1137
1054
|
});
|
|
1138
1055
|
|
|
1056
|
+
// Because no selector is specified, this rule only applies to text nodes.
|
|
1139
1057
|
// Math and code hold their content directly and do not have text nodes
|
|
1140
1058
|
// beneath them (unlike the HTML DOM) so this rule automatically does not
|
|
1141
1059
|
// apply inside $$ or ``.
|
|
1142
|
-
|
|
1143
1060
|
var UnbalancedCodeDelimiters = Rule.makeRule({
|
|
1144
1061
|
name: "unbalanced-code-delimiters",
|
|
1145
1062
|
severity: Rule.Severity.ERROR,
|
|
@@ -1164,6 +1081,7 @@ var WidgetInTable = Rule.makeRule({
|
|
|
1164
1081
|
// TODO(davidflanagan):
|
|
1165
1082
|
var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAfterTerminal, ExtraContentSpacing, HeadingLevel1, HeadingLevelSkip, HeadingSentenceCase, HeadingTitleCase, ImageAltText, ImageInTable, LinkClickHere, LongParagraph, MathAdjacent, MathAlignExtraBreak, MathAlignLinebreaks, MathEmpty, MathFontSize, MathFrac, MathNested, MathStartsWithSpace, MathTextEmpty, NestedLists, TableMissingCells, UnescapedDollar, WidgetInTable, Profanity, MathWithoutDollars, UnbalancedCodeDelimiters, ImageSpacesAroundUrls, ImageWidget];
|
|
1166
1083
|
|
|
1084
|
+
// TreeNode is the type of a node in a parse tree. The only real requirement is
|
|
1167
1085
|
// that every node has a string-valued `type` property
|
|
1168
1086
|
|
|
1169
1087
|
// This is the TreeTransformer class described in detail at the
|
|
@@ -1172,29 +1090,30 @@ class TreeTransformer {
|
|
|
1172
1090
|
// To create a tree transformer, just pass the root node of the tree
|
|
1173
1091
|
constructor(root) {
|
|
1174
1092
|
_defineProperty(this, "root", void 0);
|
|
1175
|
-
|
|
1176
1093
|
this.root = root;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1094
|
+
}
|
|
1179
1095
|
|
|
1096
|
+
// A utility function for determing whether an arbitrary value is a node
|
|
1180
1097
|
static isNode(n) {
|
|
1181
1098
|
return n && typeof n === "object" && typeof n.type === "string";
|
|
1182
|
-
}
|
|
1183
|
-
// a text-valued `content` property.
|
|
1184
|
-
|
|
1099
|
+
}
|
|
1185
1100
|
|
|
1101
|
+
// Determines whether a value is a node with type "text" and has
|
|
1102
|
+
// a text-valued `content` property.
|
|
1186
1103
|
static isTextNode(n) {
|
|
1187
1104
|
return TreeTransformer.isNode(n) && n.type === "text" && typeof n.content === "string";
|
|
1188
|
-
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// This is the main entry point for the traverse() method. See the comment
|
|
1189
1108
|
// at the top of this file for a detailed description. Note that this
|
|
1190
1109
|
// method just creates a new TraversalState object to use for this
|
|
1191
1110
|
// traversal and then invokes the internal _traverse() method to begin the
|
|
1192
1111
|
// recursion.
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
1112
|
traverse(f) {
|
|
1196
1113
|
this._traverse(this.root, new TraversalState(this.root), f);
|
|
1197
|
-
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Do a post-order traversal of node and its descendants, invoking the
|
|
1198
1117
|
// callback function f() once for each node and returning the concatenated
|
|
1199
1118
|
// text content of the node and its descendants. f() is passed three
|
|
1200
1119
|
// arguments: the current node, a TraversalState object representing the
|
|
@@ -1204,31 +1123,29 @@ class TreeTransformer {
|
|
|
1204
1123
|
// This private method holds all the traversal logic and implementation
|
|
1205
1124
|
// details. Note that this method uses the TraversalState object to store
|
|
1206
1125
|
// information about the structure of the tree.
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
_traverse( // eslint-disable-next-line ft-flow/no-mutable-array
|
|
1210
|
-
n, state, f) {
|
|
1126
|
+
_traverse(n, state, f) {
|
|
1211
1127
|
var content = "";
|
|
1212
|
-
|
|
1213
1128
|
if (TreeTransformer.isNode(n)) {
|
|
1214
1129
|
// If we were called on a node object, then we handle it
|
|
1215
1130
|
// this way.
|
|
1216
1131
|
var _node = n; // safe cast; we just tested
|
|
1217
|
-
// Put the node on the stack before recursing on its children
|
|
1218
1132
|
|
|
1133
|
+
// Put the node on the stack before recursing on its children
|
|
1219
1134
|
state._containers.push(_node);
|
|
1135
|
+
state._ancestors.push(_node);
|
|
1220
1136
|
|
|
1221
|
-
|
|
1137
|
+
// Record the node's text content if it has any.
|
|
1222
1138
|
// Usually this is for nodes with a type property of "text",
|
|
1223
1139
|
// but other nodes types like "math" may also have content.
|
|
1224
1140
|
// TODO(mdr): We found a new Flow error when upgrading:
|
|
1225
1141
|
// "node.content (property `content` is missing in `TreeNode` [1].)"
|
|
1226
|
-
//
|
|
1227
|
-
|
|
1228
|
-
|
|
1142
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
1229
1143
|
if (typeof _node.content === "string") {
|
|
1144
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
1230
1145
|
content = _node.content;
|
|
1231
|
-
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Recurse on the node. If there was content above, then there
|
|
1232
1149
|
// probably won't be any children to recurse on, but we check
|
|
1233
1150
|
// anyway.
|
|
1234
1151
|
//
|
|
@@ -1237,14 +1154,13 @@ class TreeTransformer {
|
|
|
1237
1154
|
// put a switch statement here to dispatch on the node type
|
|
1238
1155
|
// property with specific recursion steps for each known type of
|
|
1239
1156
|
// node.
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
1157
|
var keys = Object.keys(_node);
|
|
1243
1158
|
keys.forEach(key => {
|
|
1244
1159
|
// Never recurse on the type property
|
|
1245
1160
|
if (key === "type") {
|
|
1246
1161
|
return;
|
|
1247
|
-
}
|
|
1162
|
+
}
|
|
1163
|
+
// Ignore properties that are null or primitive and only
|
|
1248
1164
|
// recurse on objects and arrays. Note that we don't do a
|
|
1249
1165
|
// isNode() check here. That is done in the recursive call to
|
|
1250
1166
|
// _traverse(). Note that the recursive call on each child
|
|
@@ -1252,35 +1168,33 @@ class TreeTransformer {
|
|
|
1252
1168
|
// content to the content for this node. Also note that we
|
|
1253
1169
|
// push the name of the property we're recursing over onto a
|
|
1254
1170
|
// TraversalState stack.
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
1171
|
var value = _node[key];
|
|
1258
|
-
|
|
1259
1172
|
if (value && typeof value === "object") {
|
|
1260
1173
|
state._indexes.push(key);
|
|
1261
|
-
|
|
1262
1174
|
content += this._traverse(value, state, f);
|
|
1263
|
-
|
|
1264
1175
|
state._indexes.pop();
|
|
1265
1176
|
}
|
|
1266
|
-
});
|
|
1177
|
+
});
|
|
1267
1178
|
|
|
1179
|
+
// Restore the stacks after recursing on the children
|
|
1268
1180
|
state._currentNode = state._ancestors.pop();
|
|
1181
|
+
state._containers.pop();
|
|
1269
1182
|
|
|
1270
|
-
|
|
1183
|
+
// And finally call the traversal callback for this node. Note
|
|
1271
1184
|
// that this is post-order traversal. We call the callback on the
|
|
1272
1185
|
// way back up the tree, not on the way down. That way we already
|
|
1273
1186
|
// know all the content contained within the node.
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
1187
|
f(_node, state, content);
|
|
1277
1188
|
} else if (Array.isArray(n)) {
|
|
1278
1189
|
// If we were called on an array instead of a node, then
|
|
1279
1190
|
// this is the code we use to recurse.
|
|
1280
|
-
var nodes = n;
|
|
1191
|
+
var nodes = n;
|
|
1192
|
+
|
|
1193
|
+
// Push the array onto the stack. This will allow the
|
|
1281
1194
|
// TraversalState object to locate siblings of this node.
|
|
1195
|
+
state._containers.push(nodes);
|
|
1282
1196
|
|
|
1283
|
-
|
|
1197
|
+
// Now loop through this array and recurse on each element in it.
|
|
1284
1198
|
// Before recursing on an element, we push its array index on a
|
|
1285
1199
|
// TraversalState stack so that the TraversalState sibling methods
|
|
1286
1200
|
// can work. Note that TraversalState methods can alter the length
|
|
@@ -1288,31 +1202,28 @@ class TreeTransformer {
|
|
|
1288
1202
|
// are careful here to test the array length on each iteration and
|
|
1289
1203
|
// to reset the index when we pop the stack. Also note that we
|
|
1290
1204
|
// concatentate the text content of the children.
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
1205
|
var index = 0;
|
|
1294
|
-
|
|
1295
1206
|
while (index < nodes.length) {
|
|
1296
1207
|
state._indexes.push(index);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1208
|
+
content += this._traverse(nodes[index], state, f);
|
|
1209
|
+
// Casting to convince Flow that this is a number
|
|
1300
1210
|
index = state._indexes.pop() + 1;
|
|
1301
|
-
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Pop the array off the stack. Note, however, that we do not call
|
|
1302
1214
|
// the traversal callback on the array. That function is only
|
|
1303
1215
|
// called for nodes, not arrays of nodes.
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
1216
|
state._containers.pop();
|
|
1307
|
-
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// The _traverse() method always returns the text content of
|
|
1308
1220
|
// this node and its children. This is the one piece of state that
|
|
1309
1221
|
// is not tracked in the TraversalState object.
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
1222
|
return content;
|
|
1313
1223
|
}
|
|
1224
|
+
}
|
|
1314
1225
|
|
|
1315
|
-
|
|
1226
|
+
// An instance of this class is passed to the callback function for
|
|
1316
1227
|
// each node traversed. The class itself is not exported, but its
|
|
1317
1228
|
// methods define the API available to the traversal callback.
|
|
1318
1229
|
|
|
@@ -1324,67 +1235,65 @@ class TreeTransformer {
|
|
|
1324
1235
|
* instantiated directly, but is exported so that its type can be used for
|
|
1325
1236
|
* Flow annotaions.
|
|
1326
1237
|
**/
|
|
1327
|
-
|
|
1328
1238
|
class TraversalState {
|
|
1329
1239
|
// The root node of the tree being traversed
|
|
1240
|
+
|
|
1330
1241
|
// These are internal state properties. Use the accessor methods defined
|
|
1331
1242
|
// below instead of using these properties directly. Note that the
|
|
1332
1243
|
// _containers and _indexes stacks can have two different types of
|
|
1333
1244
|
// elements, depending on whether we just recursed on an array or on a
|
|
1334
1245
|
// node. This is hard for Flow to deal with, so you'll see a number of
|
|
1335
1246
|
// Flow casts through the any type when working with these two properties.
|
|
1336
|
-
|
|
1247
|
+
|
|
1337
1248
|
// The constructor just stores the root node and creates empty stacks.
|
|
1338
1249
|
constructor(root) {
|
|
1339
1250
|
_defineProperty(this, "root", void 0);
|
|
1340
|
-
|
|
1341
1251
|
_defineProperty(this, "_currentNode", void 0);
|
|
1342
|
-
|
|
1343
1252
|
_defineProperty(this, "_containers", void 0);
|
|
1344
|
-
|
|
1345
1253
|
_defineProperty(this, "_indexes", void 0);
|
|
1346
|
-
|
|
1347
1254
|
_defineProperty(this, "_ancestors", void 0);
|
|
1255
|
+
this.root = root;
|
|
1348
1256
|
|
|
1349
|
-
|
|
1257
|
+
// When the callback is called, this property will hold the
|
|
1350
1258
|
// node that is currently being traversed.
|
|
1259
|
+
this._currentNode = null;
|
|
1351
1260
|
|
|
1352
|
-
|
|
1261
|
+
// This is a stack of the objects and arrays that we've
|
|
1353
1262
|
// traversed through before reaching the currentNode.
|
|
1354
1263
|
// It is different than the ancestors array.
|
|
1264
|
+
this._containers = new Stack();
|
|
1355
1265
|
|
|
1356
|
-
|
|
1266
|
+
// This stack has the same number of elements as the _containers
|
|
1357
1267
|
// stack. The last element of this._indexes[] is the index of
|
|
1358
1268
|
// the current node in the object or array that is the last element
|
|
1359
1269
|
// of this._containers[]. If the last element of this._containers[] is
|
|
1360
1270
|
// an array, then the last element of this stack will be a number.
|
|
1361
1271
|
// Otherwise if the last container is an object, then the last index
|
|
1362
1272
|
// will be a string property name.
|
|
1273
|
+
this._indexes = new Stack();
|
|
1363
1274
|
|
|
1364
|
-
|
|
1275
|
+
// This is a stack of the ancestor nodes of the current one.
|
|
1365
1276
|
// It is different than the containers[] stack because it only
|
|
1366
1277
|
// includes nodes, not arrays.
|
|
1367
|
-
|
|
1368
1278
|
this._ancestors = new Stack();
|
|
1369
1279
|
}
|
|
1280
|
+
|
|
1370
1281
|
/**
|
|
1371
1282
|
* Return the current node in the traversal. Any time the traversal
|
|
1372
1283
|
* callback is called, this method will return the name value as the
|
|
1373
1284
|
* first argument to the callback.
|
|
1374
1285
|
*/
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
1286
|
currentNode() {
|
|
1378
1287
|
return this._currentNode || this.root;
|
|
1379
1288
|
}
|
|
1289
|
+
|
|
1380
1290
|
/**
|
|
1381
1291
|
* Return the parent of the current node, if there is one, or null.
|
|
1382
1292
|
*/
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
1293
|
parent() {
|
|
1386
1294
|
return this._ancestors.top();
|
|
1387
1295
|
}
|
|
1296
|
+
|
|
1388
1297
|
/**
|
|
1389
1298
|
* Return an array of ancestor nodes. The first element of this array is
|
|
1390
1299
|
* the same as this.parent() and the last element is the root node. If we
|
|
@@ -1392,79 +1301,68 @@ class TraversalState {
|
|
|
1392
1301
|
* This method makes a copy of the internal state, so modifications to the
|
|
1393
1302
|
* returned array have no effect on the traversal.
|
|
1394
1303
|
*/
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
1304
|
ancestors() {
|
|
1398
1305
|
return this._ancestors.values();
|
|
1399
1306
|
}
|
|
1307
|
+
|
|
1400
1308
|
/**
|
|
1401
1309
|
* Return the next sibling of this node, if it has one, or null otherwise.
|
|
1402
1310
|
*/
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
1311
|
nextSibling() {
|
|
1406
|
-
var siblings = this._containers.top();
|
|
1407
|
-
// object instead of an array, then there are no siblings.
|
|
1408
|
-
|
|
1312
|
+
var siblings = this._containers.top();
|
|
1409
1313
|
|
|
1314
|
+
// If we're at the root of the tree or if the parent is an
|
|
1315
|
+
// object instead of an array, then there are no siblings.
|
|
1410
1316
|
if (!siblings || !Array.isArray(siblings)) {
|
|
1411
1317
|
return null;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1318
|
+
}
|
|
1414
1319
|
|
|
1320
|
+
// The top index is a number because the top container is an array
|
|
1415
1321
|
var index = this._indexes.top();
|
|
1416
|
-
|
|
1417
1322
|
if (siblings.length > index + 1) {
|
|
1418
1323
|
return siblings[index + 1];
|
|
1419
1324
|
}
|
|
1420
|
-
|
|
1421
1325
|
return null; // There is no next sibling
|
|
1422
1326
|
}
|
|
1327
|
+
|
|
1423
1328
|
/**
|
|
1424
1329
|
* Return the previous sibling of this node, if it has one, or null
|
|
1425
1330
|
* otherwise.
|
|
1426
1331
|
*/
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
1332
|
previousSibling() {
|
|
1430
|
-
var siblings = this._containers.top();
|
|
1431
|
-
// object instead of an array, then there are no siblings.
|
|
1432
|
-
|
|
1333
|
+
var siblings = this._containers.top();
|
|
1433
1334
|
|
|
1335
|
+
// If we're at the root of the tree or if the parent is an
|
|
1336
|
+
// object instead of an array, then there are no siblings.
|
|
1434
1337
|
if (!siblings || !Array.isArray(siblings)) {
|
|
1435
1338
|
return null;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1339
|
+
}
|
|
1438
1340
|
|
|
1341
|
+
// The top index is a number because the top container is an array
|
|
1439
1342
|
var index = this._indexes.top();
|
|
1440
|
-
|
|
1441
1343
|
if (index > 0) {
|
|
1442
1344
|
return siblings[index - 1];
|
|
1443
1345
|
}
|
|
1444
|
-
|
|
1445
1346
|
return null; // There is no previous sibling
|
|
1446
1347
|
}
|
|
1348
|
+
|
|
1447
1349
|
/**
|
|
1448
1350
|
* Remove the next sibling node (if there is one) from the tree. Returns
|
|
1449
1351
|
* the removed sibling or null. This method makes it easy to traverse a
|
|
1450
1352
|
* tree and concatenate adjacent text nodes into a single node.
|
|
1451
1353
|
*/
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
1354
|
removeNextSibling() {
|
|
1455
1355
|
var siblings = this._containers.top();
|
|
1456
|
-
|
|
1457
1356
|
if (siblings && Array.isArray(siblings)) {
|
|
1458
1357
|
// top index is a number because top container is an array
|
|
1459
1358
|
var index = this._indexes.top();
|
|
1460
|
-
|
|
1461
1359
|
if (siblings.length > index + 1) {
|
|
1462
1360
|
return siblings.splice(index + 1, 1)[0];
|
|
1463
1361
|
}
|
|
1464
1362
|
}
|
|
1465
|
-
|
|
1466
1363
|
return null;
|
|
1467
1364
|
}
|
|
1365
|
+
|
|
1468
1366
|
/**
|
|
1469
1367
|
* Replace the current node in the tree with the specified nodes. If no
|
|
1470
1368
|
* nodes are passed, this is a node deletion. If one node (or array) is
|
|
@@ -1476,37 +1374,30 @@ class TraversalState {
|
|
|
1476
1374
|
* This method throws an error if you attempt to replace the root node of
|
|
1477
1375
|
* the tree.
|
|
1478
1376
|
*/
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
1377
|
replace() {
|
|
1482
1378
|
var parent = this._containers.top();
|
|
1483
|
-
|
|
1484
1379
|
if (!parent) {
|
|
1485
1380
|
throw new PerseusError("Can't replace the root of the tree", Errors.Internal);
|
|
1486
|
-
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// The top of the container stack is either an array or an object
|
|
1487
1384
|
// and the top of the indexes stack is a corresponding array index
|
|
1488
1385
|
// or object property. This is hard for Flow, so we have to do some
|
|
1489
1386
|
// unsafe casting and be careful when we use which cast version
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
1387
|
for (var _len = arguments.length, replacements = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
1493
1388
|
replacements[_key] = arguments[_key];
|
|
1494
1389
|
}
|
|
1495
|
-
|
|
1496
1390
|
if (Array.isArray(parent)) {
|
|
1497
|
-
var index = this._indexes.top();
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1391
|
+
var index = this._indexes.top();
|
|
1392
|
+
// For an array parent we just splice the new nodes in
|
|
1393
|
+
parent.splice(index, 1, ...replacements);
|
|
1394
|
+
// Adjust the index to account for the changed array length.
|
|
1501
1395
|
// We don't want to traverse any of the newly inserted nodes.
|
|
1502
|
-
|
|
1503
1396
|
this._indexes.pop();
|
|
1504
|
-
|
|
1505
1397
|
this._indexes.push(index + replacements.length - 1);
|
|
1506
1398
|
} else {
|
|
1507
|
-
var property = this._indexes.top();
|
|
1508
|
-
|
|
1509
|
-
|
|
1399
|
+
var property = this._indexes.top();
|
|
1400
|
+
// For an object parent we care how many new nodes there are
|
|
1510
1401
|
if (replacements.length === 0) {
|
|
1511
1402
|
// Deletion
|
|
1512
1403
|
delete parent[property];
|
|
@@ -1519,16 +1410,16 @@ class TraversalState {
|
|
|
1519
1410
|
}
|
|
1520
1411
|
}
|
|
1521
1412
|
}
|
|
1413
|
+
|
|
1522
1414
|
/**
|
|
1523
1415
|
* Returns true if the current node has a previous sibling and false
|
|
1524
1416
|
* otherwise. If this method returns false, then previousSibling() will
|
|
1525
1417
|
* return null, and goToPreviousSibling() will throw an error.
|
|
1526
1418
|
*/
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
1419
|
hasPreviousSibling() {
|
|
1530
1420
|
return Array.isArray(this._containers.top()) && this._indexes.top() > 0;
|
|
1531
1421
|
}
|
|
1422
|
+
|
|
1532
1423
|
/**
|
|
1533
1424
|
* Modify this traversal state object to have the state it would have had
|
|
1534
1425
|
* when visiting the previous sibling. Note that you may want to use
|
|
@@ -1537,31 +1428,27 @@ class TraversalState {
|
|
|
1537
1428
|
* traversals, but is used by the Selector class for matching multi-node
|
|
1538
1429
|
* selectors.
|
|
1539
1430
|
*/
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
1431
|
goToPreviousSibling() {
|
|
1543
1432
|
if (!this.hasPreviousSibling()) {
|
|
1544
1433
|
throw new PerseusError("goToPreviousSibling(): node has no previous sibling", Errors.Internal);
|
|
1545
1434
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1435
|
+
this._currentNode = this.previousSibling();
|
|
1436
|
+
// Since we know that we have a previous sibling, we know that
|
|
1548
1437
|
// the value on top of the stack is a number, but we have to do
|
|
1549
1438
|
// this unsafe cast because Flow doesn't know that.
|
|
1550
|
-
|
|
1551
1439
|
var index = this._indexes.pop();
|
|
1552
|
-
|
|
1553
1440
|
this._indexes.push(index - 1);
|
|
1554
1441
|
}
|
|
1442
|
+
|
|
1555
1443
|
/**
|
|
1556
1444
|
* Returns true if the current node has an ancestor and false otherwise.
|
|
1557
1445
|
* If this method returns false, then the parent() method will return
|
|
1558
1446
|
* null and goToParent() will throw an error
|
|
1559
1447
|
*/
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
1448
|
hasParent() {
|
|
1563
1449
|
return this._ancestors.size() !== 0;
|
|
1564
1450
|
}
|
|
1451
|
+
|
|
1565
1452
|
/**
|
|
1566
1453
|
* Modify this object to look like it will look when we (later) visit the
|
|
1567
1454
|
* parent node of this node. You should not modify the instance passed to
|
|
@@ -1571,33 +1458,30 @@ class TraversalState {
|
|
|
1571
1458
|
* matching multi-node selectors that involve parent and ancestor
|
|
1572
1459
|
* selectors.
|
|
1573
1460
|
*/
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
1461
|
goToParent() {
|
|
1577
1462
|
if (!this.hasParent()) {
|
|
1578
1463
|
throw new PerseusError("goToParent(): node has no ancestor", Errors.NotAllowed);
|
|
1579
1464
|
}
|
|
1465
|
+
this._currentNode = this._ancestors.pop();
|
|
1580
1466
|
|
|
1581
|
-
|
|
1467
|
+
// We need to pop the containers and indexes stacks at least once
|
|
1582
1468
|
// and more as needed until we restore the invariant that
|
|
1583
1469
|
// this._containers.top()[this.indexes.top()] === this._currentNode
|
|
1584
1470
|
//
|
|
1585
|
-
|
|
1586
|
-
|
|
1471
|
+
while (this._containers.size() &&
|
|
1472
|
+
// This is safe, but easier to just disable flow than do casts
|
|
1587
1473
|
// $FlowFixMe[incompatible-use]
|
|
1588
1474
|
this._containers.top()[this._indexes.top()] !== this._currentNode) {
|
|
1589
1475
|
this._containers.pop();
|
|
1590
|
-
|
|
1591
1476
|
this._indexes.pop();
|
|
1592
1477
|
}
|
|
1593
1478
|
}
|
|
1479
|
+
|
|
1594
1480
|
/**
|
|
1595
1481
|
* Return a new TraversalState object that is a copy of this one.
|
|
1596
1482
|
* This method is useful in conjunction with the mutating methods
|
|
1597
1483
|
* goToParent() and goToPreviousSibling().
|
|
1598
1484
|
*/
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
1485
|
clone() {
|
|
1602
1486
|
var clone = new TraversalState(this.root);
|
|
1603
1487
|
clone._currentNode = this._currentNode;
|
|
@@ -1606,18 +1490,17 @@ class TraversalState {
|
|
|
1606
1490
|
clone._ancestors = this._ancestors.clone();
|
|
1607
1491
|
return clone;
|
|
1608
1492
|
}
|
|
1493
|
+
|
|
1609
1494
|
/**
|
|
1610
1495
|
* Returns true if this TraversalState object is equal to that
|
|
1611
1496
|
* TraversalState object, or false otherwise. This method exists
|
|
1612
1497
|
* primarily for use by our unit tests.
|
|
1613
1498
|
*/
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
1499
|
equals(that) {
|
|
1617
1500
|
return this.root === that.root && this._currentNode === that._currentNode && this._containers.equals(that._containers) && this._indexes.equals(that._indexes) && this._ancestors.equals(that._ancestors);
|
|
1618
1501
|
}
|
|
1619
|
-
|
|
1620
1502
|
}
|
|
1503
|
+
|
|
1621
1504
|
/**
|
|
1622
1505
|
* This class is an internal utility that just treats an array as a stack
|
|
1623
1506
|
* and gives us a top() method so we don't have to write expressions like
|
|
@@ -1626,76 +1509,63 @@ class TraversalState {
|
|
|
1626
1509
|
* modifying our internal stacks. The use of this Stack abstraction makes
|
|
1627
1510
|
* the TraversalState class simpler in a number of places.
|
|
1628
1511
|
*/
|
|
1629
|
-
|
|
1630
1512
|
class Stack {
|
|
1631
|
-
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
1632
1513
|
constructor(array) {
|
|
1633
1514
|
_defineProperty(this, "stack", void 0);
|
|
1634
|
-
|
|
1635
1515
|
this.stack = array ? array.slice(0) : [];
|
|
1636
1516
|
}
|
|
1637
|
-
/** Push a value onto the stack. */
|
|
1638
|
-
|
|
1639
1517
|
|
|
1518
|
+
/** Push a value onto the stack. */
|
|
1640
1519
|
push(v) {
|
|
1641
1520
|
this.stack.push(v);
|
|
1642
1521
|
}
|
|
1643
|
-
/** Pop a value off of the stack. */
|
|
1644
|
-
|
|
1645
1522
|
|
|
1523
|
+
/** Pop a value off of the stack. */
|
|
1646
1524
|
pop() {
|
|
1525
|
+
// @ts-expect-error [FEI-5003] - TS2322 - Type 'T | undefined' is not assignable to type 'T'.
|
|
1647
1526
|
return this.stack.pop();
|
|
1648
1527
|
}
|
|
1649
|
-
/** Return the top value of the stack without popping it. */
|
|
1650
|
-
|
|
1651
1528
|
|
|
1529
|
+
/** Return the top value of the stack without popping it. */
|
|
1652
1530
|
top() {
|
|
1653
1531
|
return this.stack[this.stack.length - 1];
|
|
1654
1532
|
}
|
|
1655
|
-
/** Return a copy of the stack as an array */
|
|
1656
|
-
|
|
1657
1533
|
|
|
1534
|
+
/** Return a copy of the stack as an array */
|
|
1658
1535
|
values() {
|
|
1659
1536
|
return this.stack.slice(0);
|
|
1660
1537
|
}
|
|
1661
|
-
/** Return the number of elements in the stack */
|
|
1662
|
-
|
|
1663
1538
|
|
|
1539
|
+
/** Return the number of elements in the stack */
|
|
1664
1540
|
size() {
|
|
1665
1541
|
return this.stack.length;
|
|
1666
1542
|
}
|
|
1667
|
-
/** Return a string representation of the stack */
|
|
1668
|
-
|
|
1669
1543
|
|
|
1544
|
+
/** Return a string representation of the stack */
|
|
1670
1545
|
toString() {
|
|
1671
1546
|
return this.stack.toString();
|
|
1672
1547
|
}
|
|
1673
|
-
/** Return a shallow copy of the stack */
|
|
1674
|
-
|
|
1675
1548
|
|
|
1549
|
+
/** Return a shallow copy of the stack */
|
|
1676
1550
|
clone() {
|
|
1677
1551
|
return new Stack(this.stack);
|
|
1678
1552
|
}
|
|
1553
|
+
|
|
1679
1554
|
/**
|
|
1680
1555
|
* Compare this stack to another and return true if the contents of
|
|
1681
1556
|
* the two arrays are the same.
|
|
1682
1557
|
*/
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
1558
|
equals(that) {
|
|
1686
1559
|
if (!that || !that.stack || that.stack.length !== this.stack.length) {
|
|
1687
1560
|
return false;
|
|
1688
1561
|
}
|
|
1689
|
-
|
|
1690
1562
|
for (var i = 0; i < this.stack.length; i++) {
|
|
1691
1563
|
if (this.stack[i] !== that.stack[i]) {
|
|
1692
1564
|
return false;
|
|
1693
1565
|
}
|
|
1694
1566
|
}
|
|
1695
|
-
|
|
1696
1567
|
return true;
|
|
1697
1568
|
}
|
|
1698
|
-
|
|
1699
1569
|
}
|
|
1700
1570
|
|
|
1701
1571
|
// Define the shape of the linter context object that is passed through the
|
|
@@ -1713,6 +1583,8 @@ var linterContextDefault = {
|
|
|
1713
1583
|
};
|
|
1714
1584
|
|
|
1715
1585
|
var allLintRules = AllRules.filter(r => r.severity < Rule.Severity.BULK_WARNING);
|
|
1586
|
+
|
|
1587
|
+
//
|
|
1716
1588
|
// Run the Perseus linter over the specified markdown parse tree,
|
|
1717
1589
|
// with the specified context object, and
|
|
1718
1590
|
// return a (possibly empty) array of lint warning objects. If the
|
|
@@ -1736,26 +1608,26 @@ var allLintRules = AllRules.filter(r => r.severity < Rule.Severity.BULK_WARNING)
|
|
|
1736
1608
|
// in that case). This would allow the one function to be used for both
|
|
1737
1609
|
// online linting and batch linting.
|
|
1738
1610
|
//
|
|
1739
|
-
|
|
1740
1611
|
function runLinter(tree, context, highlight) {
|
|
1741
1612
|
var rules = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : allLintRules;
|
|
1742
1613
|
var warnings = [];
|
|
1743
|
-
var tt = new TreeTransformer(tree);
|
|
1744
|
-
// coalesce them before linting for efficiency and accuracy.
|
|
1614
|
+
var tt = new TreeTransformer(tree);
|
|
1745
1615
|
|
|
1616
|
+
// The markdown parser often outputs adjacent text nodes. We
|
|
1617
|
+
// coalesce them before linting for efficiency and accuracy.
|
|
1746
1618
|
tt.traverse((node, state, content) => {
|
|
1747
1619
|
if (TreeTransformer.isTextNode(node)) {
|
|
1748
1620
|
var next = state.nextSibling();
|
|
1749
|
-
|
|
1750
1621
|
while (TreeTransformer.isTextNode(next)) {
|
|
1751
|
-
//
|
|
1752
|
-
// $FlowFixMe[incompatible-use]
|
|
1622
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'. | TS2533 - Object is possibly 'null' or 'undefined'. | TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
1753
1623
|
node.content += next.content;
|
|
1754
1624
|
state.removeNextSibling();
|
|
1755
1625
|
next = state.nextSibling();
|
|
1756
1626
|
}
|
|
1757
1627
|
}
|
|
1758
|
-
});
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
// HTML tables are complicated, and the CSS we use in
|
|
1759
1631
|
// ../components/lint.jsx to display lint does not work to
|
|
1760
1632
|
// correctly position the lint indicators in the margin when the
|
|
1761
1633
|
// lint is inside a table. So as a workaround we keep track of all
|
|
@@ -1772,30 +1644,29 @@ function runLinter(tree, context, highlight) {
|
|
|
1772
1644
|
// issue too. But using JavaScript has its own downsides: there is
|
|
1773
1645
|
// risk that the linter JavaScript would interfere with
|
|
1774
1646
|
// widget-related Javascript.
|
|
1775
|
-
|
|
1776
1647
|
var tableWarnings = [];
|
|
1777
|
-
var insideTable = false;
|
|
1648
|
+
var insideTable = false;
|
|
1649
|
+
|
|
1650
|
+
// Traverse through the nodes of the parse tree. At each node, loop
|
|
1778
1651
|
// through the array of lint rules and check whether there is a
|
|
1779
1652
|
// lint violation at that node.
|
|
1780
|
-
|
|
1781
1653
|
tt.traverse((node, state, content) => {
|
|
1782
|
-
var nodeWarnings = [];
|
|
1654
|
+
var nodeWarnings = [];
|
|
1655
|
+
|
|
1656
|
+
// If our rule is only designed to be tested against a particular
|
|
1783
1657
|
// content type and we're not in that content type, we don't need to
|
|
1784
1658
|
// consider that rule.
|
|
1659
|
+
var applicableRules = rules.filter(r => r.applies(context));
|
|
1785
1660
|
|
|
1786
|
-
|
|
1661
|
+
// Generate a stack so we can identify our position in the tree in
|
|
1787
1662
|
// lint rules
|
|
1788
|
-
|
|
1789
1663
|
var stack = [...context.stack];
|
|
1790
1664
|
stack.push(node.type);
|
|
1791
|
-
|
|
1792
1665
|
var nodeContext = _objectSpread2(_objectSpread2({}, context), {}, {
|
|
1793
1666
|
stack: stack.join(".")
|
|
1794
1667
|
});
|
|
1795
|
-
|
|
1796
1668
|
applicableRules.forEach(rule => {
|
|
1797
1669
|
var warning = rule.check(node, state, content, nodeContext);
|
|
1798
|
-
|
|
1799
1670
|
if (warning) {
|
|
1800
1671
|
// The start and end locations are relative to this
|
|
1801
1672
|
// particular node, and so are not generally very useful.
|
|
@@ -1805,32 +1676,34 @@ function runLinter(tree, context, highlight) {
|
|
|
1805
1676
|
// character range that will be useful
|
|
1806
1677
|
if (warning.start || warning.end) {
|
|
1807
1678
|
warning.target = content.substring(warning.start, warning.end);
|
|
1808
|
-
}
|
|
1679
|
+
}
|
|
1809
1680
|
|
|
1681
|
+
// Add the warning to the list of all lint we've found
|
|
1682
|
+
warnings.push(warning);
|
|
1810
1683
|
|
|
1811
|
-
|
|
1684
|
+
// If we're going to be highlighting lint, then we also
|
|
1812
1685
|
// need to keep track of warnings specific to this node.
|
|
1813
|
-
|
|
1814
1686
|
if (highlight) {
|
|
1815
1687
|
nodeWarnings.push(warning);
|
|
1816
1688
|
}
|
|
1817
1689
|
}
|
|
1818
|
-
});
|
|
1819
|
-
// traversing this node.
|
|
1690
|
+
});
|
|
1820
1691
|
|
|
1692
|
+
// If we're not highlighting lint in the tree, then we're done
|
|
1693
|
+
// traversing this node.
|
|
1821
1694
|
if (!highlight) {
|
|
1822
1695
|
return;
|
|
1823
|
-
}
|
|
1824
|
-
// inside the table, then we want to add that lint here
|
|
1825
|
-
|
|
1696
|
+
}
|
|
1826
1697
|
|
|
1698
|
+
// If the node we are currently at is a table, and there was lint
|
|
1699
|
+
// inside the table, then we want to add that lint here
|
|
1827
1700
|
if (node.type === "table") {
|
|
1828
1701
|
if (tableWarnings.length) {
|
|
1829
1702
|
nodeWarnings.push(...tableWarnings);
|
|
1830
|
-
}
|
|
1831
|
-
// the warnings for the table
|
|
1832
|
-
|
|
1703
|
+
}
|
|
1833
1704
|
|
|
1705
|
+
// We're not in a table anymore, and don't have to remember
|
|
1706
|
+
// the warnings for the table
|
|
1834
1707
|
insideTable = false;
|
|
1835
1708
|
tableWarnings = [];
|
|
1836
1709
|
} else if (!insideTable) {
|
|
@@ -1841,14 +1714,17 @@ function runLinter(tree, context, highlight) {
|
|
|
1841
1714
|
// do this check each time... We can just wait until we ascend
|
|
1842
1715
|
// up to the table, then we'll know we're out of it.
|
|
1843
1716
|
insideTable = state.ancestors().some(n => n.type === "table");
|
|
1844
|
-
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// If we are inside a table and there were any warnings on
|
|
1845
1720
|
// this node, then we need to save the warnings for display
|
|
1846
1721
|
// on the table itself
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
1722
|
if (insideTable && nodeWarnings.length) {
|
|
1723
|
+
// @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
1850
1724
|
tableWarnings.push(...nodeWarnings);
|
|
1851
|
-
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// If there were any warnings on this node, and if we're highlighting
|
|
1852
1728
|
// lint, then reparent the node so we can highlight it. Note that
|
|
1853
1729
|
// a single node can have multiple warnings. If this happends we
|
|
1854
1730
|
// concatenate the warnings and newline separate them. (The lint.jsx
|
|
@@ -1861,19 +1737,17 @@ function runLinter(tree, context, highlight) {
|
|
|
1861
1737
|
// Note that even if we're inside a table, we still reparent the
|
|
1862
1738
|
// linty node so that it can be highlighted. We just make a note
|
|
1863
1739
|
// of whether this lint is inside a table or not.
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
1740
|
if (nodeWarnings.length) {
|
|
1867
1741
|
nodeWarnings.sort((a, b) => {
|
|
1868
1742
|
return a.severity - b.severity;
|
|
1869
1743
|
});
|
|
1870
|
-
|
|
1871
1744
|
if (node.type !== "text" || nodeWarnings.length > 1) {
|
|
1872
1745
|
// If the linty node is not a text node, or if there is more
|
|
1873
1746
|
// than one warning on a text node, then reparent the entire
|
|
1874
1747
|
// node under a new lint node and put the warnings there.
|
|
1875
1748
|
state.replace({
|
|
1876
1749
|
type: "lint",
|
|
1750
|
+
// @ts-expect-error [FEI-5003] - TS2345 - Argument of type '{ type: string; content: TreeNode; message: string; ruleName: any; blockHighlight: any; insideTable: boolean; severity: any; }' is not assignable to parameter of type 'TreeNode'.
|
|
1877
1751
|
content: node,
|
|
1878
1752
|
message: nodeWarnings.map(w => w.message).join("\n\n"),
|
|
1879
1753
|
ruleName: nodeWarnings[0].rule,
|
|
@@ -1907,32 +1781,27 @@ function runLinter(tree, context, highlight) {
|
|
|
1907
1781
|
// single line, so keeping them combined in that case might
|
|
1908
1782
|
// be the best thing, anyway.
|
|
1909
1783
|
//
|
|
1910
|
-
//
|
|
1784
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
1911
1785
|
var _content = node.content; // Text nodes have content
|
|
1912
|
-
|
|
1913
1786
|
var warning = nodeWarnings[0]; // There is only one warning.
|
|
1914
1787
|
// These are the lint boundaries within the content
|
|
1915
|
-
|
|
1916
1788
|
var start = warning.start || 0;
|
|
1917
1789
|
var end = warning.end || _content.length;
|
|
1918
|
-
|
|
1919
1790
|
var prefix = _content.substring(0, start);
|
|
1920
|
-
|
|
1921
1791
|
var lint = _content.substring(start, end);
|
|
1922
|
-
|
|
1923
1792
|
var suffix = _content.substring(end);
|
|
1924
|
-
|
|
1793
|
+
// TODO(FEI-5003): Give this a real type.
|
|
1925
1794
|
var replacements = []; // What we'll replace the node with
|
|
1926
|
-
// The prefix text node, if there is one
|
|
1927
1795
|
|
|
1796
|
+
// The prefix text node, if there is one
|
|
1928
1797
|
if (prefix) {
|
|
1929
1798
|
replacements.push({
|
|
1930
1799
|
type: "text",
|
|
1931
1800
|
content: prefix
|
|
1932
1801
|
});
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1802
|
+
}
|
|
1935
1803
|
|
|
1804
|
+
// The lint node wrapped around the linty text
|
|
1936
1805
|
replacements.push({
|
|
1937
1806
|
type: "lint",
|
|
1938
1807
|
content: {
|
|
@@ -1943,17 +1812,18 @@ function runLinter(tree, context, highlight) {
|
|
|
1943
1812
|
ruleName: warning.rule,
|
|
1944
1813
|
insideTable: insideTable,
|
|
1945
1814
|
severity: warning.severity
|
|
1946
|
-
});
|
|
1815
|
+
});
|
|
1947
1816
|
|
|
1817
|
+
// The suffix node, if there is one
|
|
1948
1818
|
if (suffix) {
|
|
1949
1819
|
replacements.push({
|
|
1950
1820
|
type: "text",
|
|
1951
1821
|
content: suffix
|
|
1952
1822
|
});
|
|
1953
|
-
}
|
|
1954
|
-
// nodes in the replacement array
|
|
1955
|
-
|
|
1823
|
+
}
|
|
1956
1824
|
|
|
1825
|
+
// Now replace the lint text node with the one to three
|
|
1826
|
+
// nodes in the replacement array
|
|
1957
1827
|
state.replace(...replacements);
|
|
1958
1828
|
}
|
|
1959
1829
|
}
|