@khanacademy/perseus-linter 0.0.0-PR973-20240214213540 → 0.0.0-PR992-20240214202318
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/package.json +7 -5
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -168
- package/src/README.md +0 -41
- package/src/__tests__/matcher.test.ts +0 -498
- package/src/__tests__/rule.test.ts +0 -110
- package/src/__tests__/rules.test.ts +0 -548
- package/src/__tests__/selector-parser.test.ts +0 -51
- package/src/__tests__/tree-transformer.test.ts +0 -444
- package/src/index.ts +0 -281
- package/src/proptypes.ts +0 -19
- package/src/rule.ts +0 -419
- package/src/rules/absolute-url.ts +0 -23
- package/src/rules/all-rules.ts +0 -71
- package/src/rules/blockquoted-math.ts +0 -9
- package/src/rules/blockquoted-widget.ts +0 -9
- package/src/rules/double-spacing-after-terminal.ts +0 -11
- package/src/rules/extra-content-spacing.ts +0 -11
- package/src/rules/heading-level-1.ts +0 -13
- package/src/rules/heading-level-skip.ts +0 -19
- package/src/rules/heading-sentence-case.ts +0 -10
- package/src/rules/heading-title-case.ts +0 -68
- package/src/rules/image-alt-text.ts +0 -20
- package/src/rules/image-in-table.ts +0 -9
- package/src/rules/image-spaces-around-urls.ts +0 -34
- package/src/rules/image-widget.ts +0 -49
- package/src/rules/link-click-here.ts +0 -10
- package/src/rules/lint-utils.ts +0 -47
- package/src/rules/long-paragraph.ts +0 -13
- package/src/rules/math-adjacent.ts +0 -9
- package/src/rules/math-align-extra-break.ts +0 -10
- package/src/rules/math-align-linebreaks.ts +0 -42
- package/src/rules/math-empty.ts +0 -9
- package/src/rules/math-font-size.ts +0 -11
- package/src/rules/math-frac.ts +0 -9
- package/src/rules/math-nested.ts +0 -10
- package/src/rules/math-starts-with-space.ts +0 -11
- package/src/rules/math-text-empty.ts +0 -9
- package/src/rules/math-without-dollars.ts +0 -13
- package/src/rules/nested-lists.ts +0 -10
- package/src/rules/profanity.ts +0 -9
- package/src/rules/table-missing-cells.ts +0 -19
- package/src/rules/unbalanced-code-delimiters.ts +0 -13
- package/src/rules/unescaped-dollar.ts +0 -9
- package/src/rules/widget-in-table.ts +0 -9
- package/src/selector.ts +0 -504
- package/src/tree-transformer.ts +0 -583
- package/src/types.ts +0 -7
- package/src/version.ts +0 -10
- package/tsconfig-build.json +0 -12
- package/tsconfig-build.tsbuildinfo +0 -1
package/src/index.ts
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import Rule from "./rule";
|
|
2
|
-
import AllRules from "./rules/all-rules";
|
|
3
|
-
import TreeTransformer from "./tree-transformer";
|
|
4
|
-
|
|
5
|
-
export {libVersion} from "./version";
|
|
6
|
-
|
|
7
|
-
export {linterContextProps, linterContextDefault} from "./proptypes";
|
|
8
|
-
export type {LinterContextProps} from "./types";
|
|
9
|
-
|
|
10
|
-
const allLintRules: ReadonlyArray<any> = AllRules.filter(
|
|
11
|
-
(r) => r.severity < Rule.Severity.BULK_WARNING,
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
export {Rule, allLintRules as rules};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Run the Perseus linter over the specified markdown parse tree,
|
|
18
|
-
* with the specified context object, and
|
|
19
|
-
* return a (possibly empty) array of lint warning objects. If the
|
|
20
|
-
* highlight argument is true, this function also modifies the parse
|
|
21
|
-
* tree to add "lint" nodes that can be visually rendered,
|
|
22
|
-
* highlighting the problems for the user. The optional rules argument
|
|
23
|
-
* is an array of Rule objects specifying which lint rules should be
|
|
24
|
-
* applied to this parse tree. When omitted, a default set of rules is used.
|
|
25
|
-
*
|
|
26
|
-
* The context object may have additional properties that some lint
|
|
27
|
-
* rules require:
|
|
28
|
-
*
|
|
29
|
-
* context.content is the source content string that was parsed to create
|
|
30
|
-
* the parse tree.
|
|
31
|
-
*
|
|
32
|
-
* context.widgets is the widgets object associated
|
|
33
|
-
* with the content string
|
|
34
|
-
*
|
|
35
|
-
* TODO: to make this even more general, allow the first argument to be
|
|
36
|
-
* a string and run the parser over it in that case? (but ignore highlight
|
|
37
|
-
* in that case). This would allow the one function to be used for both
|
|
38
|
-
* online linting and batch linting.
|
|
39
|
-
*/
|
|
40
|
-
export function runLinter(
|
|
41
|
-
tree: any,
|
|
42
|
-
context: any,
|
|
43
|
-
highlight: boolean,
|
|
44
|
-
rules: ReadonlyArray<any> = allLintRules,
|
|
45
|
-
): ReadonlyArray<any> {
|
|
46
|
-
const warnings: Array<any> = [];
|
|
47
|
-
const tt = new TreeTransformer(tree);
|
|
48
|
-
|
|
49
|
-
// The markdown parser often outputs adjacent text nodes. We
|
|
50
|
-
// coalesce them before linting for efficiency and accuracy.
|
|
51
|
-
tt.traverse((node, state, content) => {
|
|
52
|
-
if (TreeTransformer.isTextNode(node)) {
|
|
53
|
-
let next = state.nextSibling();
|
|
54
|
-
while (TreeTransformer.isTextNode(next)) {
|
|
55
|
-
// @ts-expect-error - 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'.
|
|
56
|
-
node.content += next.content;
|
|
57
|
-
state.removeNextSibling();
|
|
58
|
-
next = state.nextSibling();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// HTML tables are complicated, and the CSS we use in
|
|
64
|
-
// ../components/lint.jsx to display lint does not work to
|
|
65
|
-
// correctly position the lint indicators in the margin when the
|
|
66
|
-
// lint is inside a table. So as a workaround we keep track of all
|
|
67
|
-
// the lint that appears within a table and move it up to the
|
|
68
|
-
// table element itself.
|
|
69
|
-
//
|
|
70
|
-
// It is not ideal to have to do this here,
|
|
71
|
-
// but it is cleaner here than fixing up the lint during rendering
|
|
72
|
-
// in perseus-markdown.jsx. If our lint display was simpler and
|
|
73
|
-
// did not require indicators in the margin, this wouldn't be a
|
|
74
|
-
// problem. Or, if we modified the lint display stuff so that
|
|
75
|
-
// indicator positioning and tooltip display were both handled
|
|
76
|
-
// with JavaScript (instead of pure CSS), then we could avoid this
|
|
77
|
-
// issue too. But using JavaScript has its own downsides: there is
|
|
78
|
-
// risk that the linter JavaScript would interfere with
|
|
79
|
-
// widget-related Javascript.
|
|
80
|
-
let tableWarnings: Array<never> = [];
|
|
81
|
-
let insideTable = false;
|
|
82
|
-
|
|
83
|
-
// Traverse through the nodes of the parse tree. At each node, loop
|
|
84
|
-
// through the array of lint rules and check whether there is a
|
|
85
|
-
// lint violation at that node.
|
|
86
|
-
tt.traverse((node, state, content) => {
|
|
87
|
-
const nodeWarnings: Array<any> = [];
|
|
88
|
-
|
|
89
|
-
// If our rule is only designed to be tested against a particular
|
|
90
|
-
// content type and we're not in that content type, we don't need to
|
|
91
|
-
// consider that rule.
|
|
92
|
-
const applicableRules = rules.filter((r) => r.applies(context));
|
|
93
|
-
|
|
94
|
-
// Generate a stack so we can identify our position in the tree in
|
|
95
|
-
// lint rules
|
|
96
|
-
const stack = [...context.stack];
|
|
97
|
-
stack.push(node.type);
|
|
98
|
-
|
|
99
|
-
const nodeContext = {
|
|
100
|
-
...context,
|
|
101
|
-
stack: stack.join("."),
|
|
102
|
-
} as const;
|
|
103
|
-
|
|
104
|
-
applicableRules.forEach((rule) => {
|
|
105
|
-
const warning = rule.check(node, state, content, nodeContext);
|
|
106
|
-
if (warning) {
|
|
107
|
-
// The start and end locations are relative to this
|
|
108
|
-
// particular node, and so are not generally very useful.
|
|
109
|
-
// TODO: When the markdown parser saves the node
|
|
110
|
-
// locations in the source string then we can add
|
|
111
|
-
// these numbers to that one and get and absolute
|
|
112
|
-
// character range that will be useful
|
|
113
|
-
if (warning.start || warning.end) {
|
|
114
|
-
warning.target = content.substring(
|
|
115
|
-
warning.start,
|
|
116
|
-
warning.end,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Add the warning to the list of all lint we've found
|
|
121
|
-
warnings.push(warning);
|
|
122
|
-
|
|
123
|
-
// If we're going to be highlighting lint, then we also
|
|
124
|
-
// need to keep track of warnings specific to this node.
|
|
125
|
-
if (highlight) {
|
|
126
|
-
nodeWarnings.push(warning);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// If we're not highlighting lint in the tree, then we're done
|
|
132
|
-
// traversing this node.
|
|
133
|
-
if (!highlight) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// If the node we are currently at is a table, and there was lint
|
|
138
|
-
// inside the table, then we want to add that lint here
|
|
139
|
-
if (node.type === "table") {
|
|
140
|
-
if (tableWarnings.length) {
|
|
141
|
-
nodeWarnings.push(...tableWarnings);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// We're not in a table anymore, and don't have to remember
|
|
145
|
-
// the warnings for the table
|
|
146
|
-
insideTable = false;
|
|
147
|
-
tableWarnings = [];
|
|
148
|
-
} else if (!insideTable) {
|
|
149
|
-
// Otherwise, if we are not already inside a table, check
|
|
150
|
-
// to see if we've entered one. Because this is a post-order
|
|
151
|
-
// traversal we'll see the table contents before the table itself.
|
|
152
|
-
// Note that once we're inside the table, we don't have to
|
|
153
|
-
// do this check each time... We can just wait until we ascend
|
|
154
|
-
// up to the table, then we'll know we're out of it.
|
|
155
|
-
insideTable = state.ancestors().some((n) => n.type === "table");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// If we are inside a table and there were any warnings on
|
|
159
|
-
// this node, then we need to save the warnings for display
|
|
160
|
-
// on the table itself
|
|
161
|
-
if (insideTable && nodeWarnings.length) {
|
|
162
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
163
|
-
tableWarnings.push(...nodeWarnings);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// If there were any warnings on this node, and if we're highlighting
|
|
167
|
-
// lint, then reparent the node so we can highlight it. Note that
|
|
168
|
-
// a single node can have multiple warnings. If this happends we
|
|
169
|
-
// concatenate the warnings and newline separate them. (The lint.jsx
|
|
170
|
-
// component that displays the warnings may want to convert the
|
|
171
|
-
// newlines into <br> tags.) We also provide a lint rule name
|
|
172
|
-
// so that lint.jsx can link to a document that provides more details
|
|
173
|
-
// on that particular lint rule. If there is more than one warning
|
|
174
|
-
// we only link to the first rule, however.
|
|
175
|
-
//
|
|
176
|
-
// Note that even if we're inside a table, we still reparent the
|
|
177
|
-
// linty node so that it can be highlighted. We just make a note
|
|
178
|
-
// of whether this lint is inside a table or not.
|
|
179
|
-
if (nodeWarnings.length) {
|
|
180
|
-
nodeWarnings.sort((a, b) => {
|
|
181
|
-
return a.severity - b.severity;
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (node.type !== "text" || nodeWarnings.length > 1) {
|
|
185
|
-
// If the linty node is not a text node, or if there is more
|
|
186
|
-
// than one warning on a text node, then reparent the entire
|
|
187
|
-
// node under a new lint node and put the warnings there.
|
|
188
|
-
state.replace({
|
|
189
|
-
type: "lint",
|
|
190
|
-
// @ts-expect-error - 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'.
|
|
191
|
-
content: node,
|
|
192
|
-
message: nodeWarnings.map((w) => w.message).join("\n\n"),
|
|
193
|
-
ruleName: nodeWarnings[0].rule,
|
|
194
|
-
blockHighlight: nodeContext.blockHighlight,
|
|
195
|
-
insideTable: insideTable,
|
|
196
|
-
severity: nodeWarnings[0].severity,
|
|
197
|
-
});
|
|
198
|
-
} else {
|
|
199
|
-
//
|
|
200
|
-
// Otherwise, it is a single warning on a text node, and we
|
|
201
|
-
// only want to highlight the actual linty part of that string
|
|
202
|
-
// of text. So we want to replace the text node with (in the
|
|
203
|
-
// general case) three nodes:
|
|
204
|
-
//
|
|
205
|
-
// 1) A new text node that holds the non-linty prefix
|
|
206
|
-
//
|
|
207
|
-
// 2) A lint node that is the parent of a new text node
|
|
208
|
-
// that holds the linty part
|
|
209
|
-
//
|
|
210
|
-
// 3) A new text node that holds the non-linty suffix
|
|
211
|
-
//
|
|
212
|
-
// If the lint begins and/or ends at the boundaries of the
|
|
213
|
-
// original text node, then nodes 1 and/or 3 won't exist, of
|
|
214
|
-
// course.
|
|
215
|
-
//
|
|
216
|
-
// Note that we could generalize this to work with multple
|
|
217
|
-
// warnings on a text node as long as the warnings are
|
|
218
|
-
// non-overlapping. Hopefully, though, multiple warnings in a
|
|
219
|
-
// single text node will be rare in practice. Also, we don't
|
|
220
|
-
// have a good way to display multiple lint indicators on a
|
|
221
|
-
// single line, so keeping them combined in that case might
|
|
222
|
-
// be the best thing, anyway.
|
|
223
|
-
//
|
|
224
|
-
// @ts-expect-error - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
225
|
-
const content = node.content; // Text nodes have content
|
|
226
|
-
const warning = nodeWarnings[0]; // There is only one warning.
|
|
227
|
-
// These are the lint boundaries within the content
|
|
228
|
-
const start = warning.start || 0;
|
|
229
|
-
const end = warning.end || content.length;
|
|
230
|
-
const prefix = content.substring(0, start);
|
|
231
|
-
const lint = content.substring(start, end);
|
|
232
|
-
const suffix = content.substring(end);
|
|
233
|
-
// TODO(FEI-5003): Give this a real type.
|
|
234
|
-
const replacements: any[] = []; // What we'll replace the node with
|
|
235
|
-
|
|
236
|
-
// The prefix text node, if there is one
|
|
237
|
-
if (prefix) {
|
|
238
|
-
replacements.push({
|
|
239
|
-
type: "text",
|
|
240
|
-
content: prefix,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// The lint node wrapped around the linty text
|
|
245
|
-
replacements.push({
|
|
246
|
-
type: "lint",
|
|
247
|
-
content: {
|
|
248
|
-
type: "text",
|
|
249
|
-
content: lint,
|
|
250
|
-
},
|
|
251
|
-
message: warning.message,
|
|
252
|
-
ruleName: warning.rule,
|
|
253
|
-
insideTable: insideTable,
|
|
254
|
-
severity: warning.severity,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// The suffix node, if there is one
|
|
258
|
-
if (suffix) {
|
|
259
|
-
replacements.push({
|
|
260
|
-
type: "text",
|
|
261
|
-
content: suffix,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Now replace the lint text node with the one to three
|
|
266
|
-
// nodes in the replacement array
|
|
267
|
-
state.replace(...replacements);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
return warnings;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export function pushContextStack(context: any, name: string): any {
|
|
276
|
-
const stack = context.stack || [];
|
|
277
|
-
return {
|
|
278
|
-
...context,
|
|
279
|
-
stack: stack.concat(name),
|
|
280
|
-
};
|
|
281
|
-
}
|
package/src/proptypes.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Define the shape of the linter context object that is passed through the
|
|
2
|
-
// tree with additional information about what we are checking.
|
|
3
|
-
import PropTypes from "prop-types";
|
|
4
|
-
|
|
5
|
-
import type {LinterContextProps} from "./types";
|
|
6
|
-
|
|
7
|
-
export const linterContextProps = PropTypes.shape({
|
|
8
|
-
contentType: PropTypes.string,
|
|
9
|
-
highlightLint: PropTypes.bool,
|
|
10
|
-
paths: PropTypes.arrayOf(PropTypes.string),
|
|
11
|
-
stack: PropTypes.arrayOf(PropTypes.string),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export const linterContextDefault: LinterContextProps = {
|
|
15
|
-
contentType: "",
|
|
16
|
-
highlightLint: false,
|
|
17
|
-
paths: [] as ReadonlyArray<any>,
|
|
18
|
-
stack: [] as ReadonlyArray<any>,
|
|
19
|
-
};
|