@khanacademy/perseus-linter 0.0.0-PR973-20240207204548 → 0.0.0-PR973-20240207204934

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.
Files changed (51) hide show
  1. package/package.json +7 -4
  2. package/.eslintrc.js +0 -12
  3. package/CHANGELOG.md +0 -168
  4. package/src/README.md +0 -41
  5. package/src/__tests__/matcher.test.ts +0 -498
  6. package/src/__tests__/rule.test.ts +0 -110
  7. package/src/__tests__/rules.test.ts +0 -548
  8. package/src/__tests__/selector-parser.test.ts +0 -51
  9. package/src/__tests__/tree-transformer.test.ts +0 -444
  10. package/src/index.ts +0 -281
  11. package/src/proptypes.ts +0 -19
  12. package/src/rule.ts +0 -419
  13. package/src/rules/absolute-url.ts +0 -23
  14. package/src/rules/all-rules.ts +0 -71
  15. package/src/rules/blockquoted-math.ts +0 -9
  16. package/src/rules/blockquoted-widget.ts +0 -9
  17. package/src/rules/double-spacing-after-terminal.ts +0 -11
  18. package/src/rules/extra-content-spacing.ts +0 -11
  19. package/src/rules/heading-level-1.ts +0 -13
  20. package/src/rules/heading-level-skip.ts +0 -19
  21. package/src/rules/heading-sentence-case.ts +0 -10
  22. package/src/rules/heading-title-case.ts +0 -68
  23. package/src/rules/image-alt-text.ts +0 -20
  24. package/src/rules/image-in-table.ts +0 -9
  25. package/src/rules/image-spaces-around-urls.ts +0 -34
  26. package/src/rules/image-widget.ts +0 -49
  27. package/src/rules/link-click-here.ts +0 -10
  28. package/src/rules/lint-utils.ts +0 -47
  29. package/src/rules/long-paragraph.ts +0 -13
  30. package/src/rules/math-adjacent.ts +0 -9
  31. package/src/rules/math-align-extra-break.ts +0 -10
  32. package/src/rules/math-align-linebreaks.ts +0 -42
  33. package/src/rules/math-empty.ts +0 -9
  34. package/src/rules/math-font-size.ts +0 -11
  35. package/src/rules/math-frac.ts +0 -9
  36. package/src/rules/math-nested.ts +0 -10
  37. package/src/rules/math-starts-with-space.ts +0 -11
  38. package/src/rules/math-text-empty.ts +0 -9
  39. package/src/rules/math-without-dollars.ts +0 -13
  40. package/src/rules/nested-lists.ts +0 -10
  41. package/src/rules/profanity.ts +0 -9
  42. package/src/rules/table-missing-cells.ts +0 -19
  43. package/src/rules/unbalanced-code-delimiters.ts +0 -13
  44. package/src/rules/unescaped-dollar.ts +0 -9
  45. package/src/rules/widget-in-table.ts +0 -9
  46. package/src/selector.ts +0 -504
  47. package/src/tree-transformer.ts +0 -583
  48. package/src/types.ts +0 -7
  49. package/src/version.ts +0 -10
  50. package/tsconfig-build.json +0 -12
  51. 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
- };