@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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable no-useless-escape */
|
|
2
|
-
// @flow
|
|
3
2
|
// Return the portion of a URL between // and /. This is the authority
|
|
4
3
|
// portion which is usually just the hostname, but may also include
|
|
5
4
|
// a username, password or port. We don't strip those things out because
|
|
@@ -37,7 +36,7 @@ const internalDomains = {
|
|
|
37
36
|
"ka-mobile.s3.amazonaws.com": true,
|
|
38
37
|
"ka-perseus-graphie.s3.amazonaws.com": true,
|
|
39
38
|
"ka-perseus-images.s3.amazonaws.com": true,
|
|
40
|
-
};
|
|
39
|
+
} as const;
|
|
41
40
|
|
|
42
41
|
// Returns true if this URL is relative, or if it is an absolute
|
|
43
42
|
// URL with one of the domains listed above as its hostname.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "long-paragraph",
|
|
6
5
|
severity: Rule.Severity.GUIDELINE,
|
|
7
6
|
selector: "paragraph",
|
|
@@ -11,4 +10,4 @@ export default (Rule.makeRule({
|
|
|
11
10
|
This paragraph is ${content.length} characters long.
|
|
12
11
|
Shorten it to 500 characters or fewer.`;
|
|
13
12
|
},
|
|
14
|
-
})
|
|
13
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-adjacent",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "blockMath+blockMath",
|
|
8
7
|
message: `Adjacent math blocks:
|
|
9
8
|
combine the blocks between \\begin{align} and \\end{align}`,
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-align-extra-break",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "blockMath",
|
|
8
7
|
pattern: /(\\{2,})\s*\\end{align}/,
|
|
9
8
|
message: `Extra space at end of block:
|
|
10
9
|
Don't end an align block with backslashes`,
|
|
11
|
-
})
|
|
10
|
+
}) as Rule;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-align-linebreaks",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "blockMath",
|
|
@@ -40,4 +39,4 @@ export default (Rule.makeRule({
|
|
|
40
39
|
text = text.substring(nextpair[0].length);
|
|
41
40
|
}
|
|
42
41
|
},
|
|
43
|
-
})
|
|
42
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-empty",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "math, blockMath",
|
|
8
7
|
pattern: /^$/,
|
|
9
8
|
message: "Empty math: don't use $$ in your markdown.",
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-font-size",
|
|
6
5
|
severity: Rule.Severity.GUIDELINE,
|
|
7
6
|
selector: "math, blockMath",
|
|
@@ -9,4 +8,4 @@ export default (Rule.makeRule({
|
|
|
9
8
|
/\\(tiny|Tiny|small|large|Large|LARGE|huge|Huge|scriptsize|normalsize)\s*{/,
|
|
10
9
|
message: `Math font size:
|
|
11
10
|
Don't change the default font size with \\Large{} or similar commands`,
|
|
12
|
-
})
|
|
11
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-frac",
|
|
6
5
|
severity: Rule.Severity.GUIDELINE,
|
|
7
6
|
selector: "math, blockMath",
|
|
8
7
|
pattern: /\\frac[ {]/,
|
|
9
8
|
message: "Use \\dfrac instead of \\frac in your math expressions.",
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-nested",
|
|
6
5
|
severity: Rule.Severity.ERROR,
|
|
7
6
|
selector: "math, blockMath",
|
|
8
7
|
pattern: /\\text{[^$}]*\$[^$}]*\$[^}]*}/,
|
|
9
8
|
message: `Nested math:
|
|
10
9
|
Don't nest math expressions inside \\text{} blocks`,
|
|
11
|
-
})
|
|
10
|
+
}) as Rule;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-starts-with-space",
|
|
6
5
|
severity: Rule.Severity.GUIDELINE,
|
|
7
6
|
selector: "math, blockMath",
|
|
@@ -9,4 +8,4 @@ export default (Rule.makeRule({
|
|
|
9
8
|
message: `Math starts with space:
|
|
10
9
|
math should not be indented. Do not begin math expressions with
|
|
11
10
|
LaTeX space commands like ~, \\;, \\quad, or \\phantom`,
|
|
12
|
-
})
|
|
11
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "math-text-empty",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "math, blockMath",
|
|
8
7
|
pattern: /\\text{\s*}/,
|
|
9
8
|
message: "Empty \\text{} block in math expression",
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
3
|
// Because no selector is specified, this rule only applies to text nodes.
|
|
5
4
|
// Math and code hold their content directly and do not have text nodes
|
|
6
5
|
// beneath them (unlike the HTML DOM) so this rule automatically does not
|
|
7
6
|
// apply inside $$ or ``.
|
|
8
|
-
export default
|
|
7
|
+
export default Rule.makeRule({
|
|
9
8
|
name: "math-without-dollars",
|
|
10
9
|
severity: Rule.Severity.GUIDELINE,
|
|
11
10
|
pattern: /\\\w+{[^}]*}|{|}/,
|
|
12
11
|
message: `This looks like LaTeX:
|
|
13
12
|
did you mean to put it inside dollar signs?`,
|
|
14
|
-
})
|
|
13
|
+
}) as Rule;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "nested-lists",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "list list",
|
|
8
7
|
message: `Nested lists:
|
|
9
8
|
nested lists are hard to read on mobile devices;
|
|
10
9
|
do not use additional indentation.`,
|
|
11
|
-
})
|
|
10
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "profanity",
|
|
6
5
|
// This list could obviously be expanded a lot, but I figured we
|
|
7
6
|
// could start with https://en.wikipedia.org/wiki/Seven_dirty_words
|
|
8
7
|
pattern: /\b(shit|piss|fuck|cunt|cocksucker|motherfucker|tits)\b/i,
|
|
9
8
|
message: "Avoid profanity",
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "table-missing-cells",
|
|
6
5
|
severity: Rule.Severity.WARNING,
|
|
7
6
|
selector: "table",
|
|
@@ -17,4 +16,4 @@ Row ${r + 1} has ${rowLengths[r]} cells.`;
|
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
},
|
|
20
|
-
})
|
|
19
|
+
}) as Rule;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
3
|
// Because no selector is specified, this rule only applies to text nodes.
|
|
5
4
|
// Math and code hold their content directly and do not have text nodes
|
|
6
5
|
// beneath them (unlike the HTML DOM) so this rule automatically does not
|
|
7
6
|
// apply inside $$ or ``.
|
|
8
|
-
export default
|
|
7
|
+
export default Rule.makeRule({
|
|
9
8
|
name: "unbalanced-code-delimiters",
|
|
10
9
|
severity: Rule.Severity.ERROR,
|
|
11
10
|
pattern: /[`~]+/,
|
|
12
11
|
message: `Unbalanced code delimiters:
|
|
13
12
|
code blocks should begin and end with the same type and number of delimiters`,
|
|
14
|
-
})
|
|
13
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "unescaped-dollar",
|
|
6
5
|
severity: Rule.Severity.ERROR,
|
|
7
6
|
selector: "unescapedDollar",
|
|
8
7
|
message: `Unescaped dollar sign:
|
|
9
8
|
Dollar signs must appear in pairs or be escaped as \\$`,
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import Rule from "../rule.js";
|
|
1
|
+
import Rule from "../rule";
|
|
3
2
|
|
|
4
|
-
export default
|
|
3
|
+
export default Rule.makeRule({
|
|
5
4
|
name: "widget-in-table",
|
|
6
5
|
severity: Rule.Severity.BULK_WARNING,
|
|
7
6
|
selector: "table widget",
|
|
8
7
|
message: `Widget in table:
|
|
9
8
|
do not put widgets inside of tables.`,
|
|
10
|
-
})
|
|
9
|
+
}) as Rule;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable no-useless-escape */
|
|
2
|
-
// @flow
|
|
3
2
|
/**
|
|
4
3
|
* The Selector class implements a CSS-like system for matching nodes in a
|
|
5
4
|
* parse tree based on the structure of the tree. Create a Selector object by
|
|
@@ -89,7 +88,7 @@
|
|
|
89
88
|
|
|
90
89
|
import {Errors, PerseusError} from "@khanacademy/perseus-error";
|
|
91
90
|
|
|
92
|
-
import type {TreeNode, TraversalState} from "./tree-transformer
|
|
91
|
+
import type {TreeNode, TraversalState} from "./tree-transformer";
|
|
93
92
|
|
|
94
93
|
/**
|
|
95
94
|
* This is the base class for all Selector types. The key method that all
|
|
@@ -108,7 +107,7 @@ export default class Selector {
|
|
|
108
107
|
* This is the base class so we just throw an exception. All Selector
|
|
109
108
|
* subclasses must provide an implementation of this method.
|
|
110
109
|
*/
|
|
111
|
-
match(state: TraversalState):
|
|
110
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
112
111
|
throw new PerseusError(
|
|
113
112
|
"Selector subclasses must implement match()",
|
|
114
113
|
Errors.NotAllowed,
|
|
@@ -135,7 +134,7 @@ export default class Selector {
|
|
|
135
134
|
*/
|
|
136
135
|
class Parser {
|
|
137
136
|
static TOKENS: RegExp; // We do lexing with a simple regular expression
|
|
138
|
-
tokens:
|
|
137
|
+
tokens: ReadonlyArray<string>; // The array of tokens
|
|
139
138
|
tokenIndex: number; // Which token in the array we're looking at now
|
|
140
139
|
|
|
141
140
|
constructor(s: string) {
|
|
@@ -300,14 +299,14 @@ class ParseError extends Error {
|
|
|
300
299
|
* first.
|
|
301
300
|
*/
|
|
302
301
|
class SelectorList extends Selector {
|
|
303
|
-
selectors:
|
|
302
|
+
selectors: ReadonlyArray<Selector>;
|
|
304
303
|
|
|
305
|
-
constructor(selectors:
|
|
304
|
+
constructor(selectors: ReadonlyArray<Selector>) {
|
|
306
305
|
super();
|
|
307
306
|
this.selectors = selectors;
|
|
308
307
|
}
|
|
309
308
|
|
|
310
|
-
match(state: TraversalState):
|
|
309
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
311
310
|
for (let i = 0; i < this.selectors.length; i++) {
|
|
312
311
|
const s = this.selectors[i];
|
|
313
312
|
const result = s.match(state);
|
|
@@ -333,7 +332,7 @@ class SelectorList extends Selector {
|
|
|
333
332
|
* matches any node.
|
|
334
333
|
*/
|
|
335
334
|
class AnyNode extends Selector {
|
|
336
|
-
match(state: TraversalState):
|
|
335
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
337
336
|
return [state.currentNode()];
|
|
338
337
|
}
|
|
339
338
|
|
|
@@ -354,7 +353,7 @@ class TypeSelector extends Selector {
|
|
|
354
353
|
this.type = type;
|
|
355
354
|
}
|
|
356
355
|
|
|
357
|
-
match(state: TraversalState):
|
|
356
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
358
357
|
const node = state.currentNode();
|
|
359
358
|
if (node.type === this.type) {
|
|
360
359
|
return [node];
|
|
@@ -394,7 +393,7 @@ class AncestorCombinator extends SelectorCombinator {
|
|
|
394
393
|
super(left, right);
|
|
395
394
|
}
|
|
396
395
|
|
|
397
|
-
match(state: TraversalState):
|
|
396
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
398
397
|
const rightResult = this.right.match(state);
|
|
399
398
|
if (rightResult) {
|
|
400
399
|
state = state.clone();
|
|
@@ -424,7 +423,7 @@ class ParentCombinator extends SelectorCombinator {
|
|
|
424
423
|
super(left, right);
|
|
425
424
|
}
|
|
426
425
|
|
|
427
|
-
match(state: TraversalState):
|
|
426
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
428
427
|
const rightResult = this.right.match(state);
|
|
429
428
|
if (rightResult) {
|
|
430
429
|
if (state.hasParent()) {
|
|
@@ -454,7 +453,7 @@ class PreviousCombinator extends SelectorCombinator {
|
|
|
454
453
|
super(left, right);
|
|
455
454
|
}
|
|
456
455
|
|
|
457
|
-
match(state: TraversalState):
|
|
456
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
458
457
|
const rightResult = this.right.match(state);
|
|
459
458
|
if (rightResult) {
|
|
460
459
|
if (state.hasPreviousSibling()) {
|
|
@@ -484,7 +483,7 @@ class SiblingCombinator extends SelectorCombinator {
|
|
|
484
483
|
super(left, right);
|
|
485
484
|
}
|
|
486
485
|
|
|
487
|
-
match(state: TraversalState):
|
|
486
|
+
match(state: TraversalState): ReadonlyArray<TreeNode> | null | undefined {
|
|
488
487
|
const rightResult = this.right.match(state);
|
|
489
488
|
if (rightResult) {
|
|
490
489
|
state = state.clone();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
/**
|
|
3
2
|
* TreeTransformer is a class for traversing and transforming trees. Create a
|
|
4
3
|
* TreeTransformer by passing the root node of the tree to the
|
|
@@ -61,7 +60,9 @@ import {Errors, PerseusError} from "@khanacademy/perseus-error";
|
|
|
61
60
|
|
|
62
61
|
// TreeNode is the type of a node in a parse tree. The only real requirement is
|
|
63
62
|
// that every node has a string-valued `type` property
|
|
64
|
-
export type TreeNode = {
|
|
63
|
+
export type TreeNode = {
|
|
64
|
+
type: string;
|
|
65
|
+
};
|
|
65
66
|
|
|
66
67
|
// TraversalCallback is the type of the callback function passed to the
|
|
67
68
|
// traverse() method. It is invoked with node, state, and content arguments
|
|
@@ -117,7 +118,6 @@ export default class TreeTransformer {
|
|
|
117
118
|
// details. Note that this method uses the TraversalState object to store
|
|
118
119
|
// information about the structure of the tree.
|
|
119
120
|
_traverse(
|
|
120
|
-
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
121
121
|
n: TreeNode | Array<TreeNode>,
|
|
122
122
|
state: TraversalState,
|
|
123
123
|
f: TraversalCallback,
|
|
@@ -126,7 +126,7 @@ export default class TreeTransformer {
|
|
|
126
126
|
if (TreeTransformer.isNode(n)) {
|
|
127
127
|
// If we were called on a node object, then we handle it
|
|
128
128
|
// this way.
|
|
129
|
-
const node =
|
|
129
|
+
const node = n as TreeNode; // safe cast; we just tested
|
|
130
130
|
|
|
131
131
|
// Put the node on the stack before recursing on its children
|
|
132
132
|
state._containers.push(node);
|
|
@@ -137,8 +137,9 @@ export default class TreeTransformer {
|
|
|
137
137
|
// but other nodes types like "math" may also have content.
|
|
138
138
|
// TODO(mdr): We found a new Flow error when upgrading:
|
|
139
139
|
// "node.content (property `content` is missing in `TreeNode` [1].)"
|
|
140
|
-
//
|
|
140
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
141
141
|
if (typeof node.content === "string") {
|
|
142
|
+
// @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
|
|
142
143
|
content = node.content;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -204,7 +205,7 @@ export default class TreeTransformer {
|
|
|
204
205
|
state._indexes.push(index);
|
|
205
206
|
content += this._traverse(nodes[index], state, f);
|
|
206
207
|
// Casting to convince Flow that this is a number
|
|
207
|
-
index = (
|
|
208
|
+
index = (state._indexes.pop() as number) + 1;
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
// Pop the array off the stack. Note, however, that we do not call
|
|
@@ -242,8 +243,7 @@ export class TraversalState {
|
|
|
242
243
|
// elements, depending on whether we just recursed on an array or on a
|
|
243
244
|
// node. This is hard for Flow to deal with, so you'll see a number of
|
|
244
245
|
// Flow casts through the any type when working with these two properties.
|
|
245
|
-
_currentNode:
|
|
246
|
-
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
246
|
+
_currentNode: TreeNode | null | undefined;
|
|
247
247
|
_containers: Stack<TreeNode | Array<TreeNode>>;
|
|
248
248
|
_indexes: Stack<string | number>;
|
|
249
249
|
_ancestors: Stack<TreeNode>;
|
|
@@ -288,7 +288,7 @@ export class TraversalState {
|
|
|
288
288
|
/**
|
|
289
289
|
* Return the parent of the current node, if there is one, or null.
|
|
290
290
|
*/
|
|
291
|
-
parent():
|
|
291
|
+
parent(): TreeNode | null | undefined {
|
|
292
292
|
return this._ancestors.top();
|
|
293
293
|
}
|
|
294
294
|
|
|
@@ -299,14 +299,14 @@ export class TraversalState {
|
|
|
299
299
|
* This method makes a copy of the internal state, so modifications to the
|
|
300
300
|
* returned array have no effect on the traversal.
|
|
301
301
|
*/
|
|
302
|
-
ancestors():
|
|
302
|
+
ancestors(): ReadonlyArray<TreeNode> {
|
|
303
303
|
return this._ancestors.values();
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
/**
|
|
307
307
|
* Return the next sibling of this node, if it has one, or null otherwise.
|
|
308
308
|
*/
|
|
309
|
-
nextSibling():
|
|
309
|
+
nextSibling(): TreeNode | null | undefined {
|
|
310
310
|
const siblings = this._containers.top();
|
|
311
311
|
|
|
312
312
|
// If we're at the root of the tree or if the parent is an
|
|
@@ -316,7 +316,7 @@ export class TraversalState {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// The top index is a number because the top container is an array
|
|
319
|
-
const index =
|
|
319
|
+
const index = this._indexes.top() as number;
|
|
320
320
|
if (siblings.length > index + 1) {
|
|
321
321
|
return siblings[index + 1];
|
|
322
322
|
}
|
|
@@ -327,7 +327,7 @@ export class TraversalState {
|
|
|
327
327
|
* Return the previous sibling of this node, if it has one, or null
|
|
328
328
|
* otherwise.
|
|
329
329
|
*/
|
|
330
|
-
previousSibling():
|
|
330
|
+
previousSibling(): TreeNode | null | undefined {
|
|
331
331
|
const siblings = this._containers.top();
|
|
332
332
|
|
|
333
333
|
// If we're at the root of the tree or if the parent is an
|
|
@@ -337,7 +337,7 @@ export class TraversalState {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
// The top index is a number because the top container is an array
|
|
340
|
-
const index =
|
|
340
|
+
const index = this._indexes.top() as number;
|
|
341
341
|
if (index > 0) {
|
|
342
342
|
return siblings[index - 1];
|
|
343
343
|
}
|
|
@@ -349,11 +349,11 @@ export class TraversalState {
|
|
|
349
349
|
* the removed sibling or null. This method makes it easy to traverse a
|
|
350
350
|
* tree and concatenate adjacent text nodes into a single node.
|
|
351
351
|
*/
|
|
352
|
-
removeNextSibling():
|
|
352
|
+
removeNextSibling(): TreeNode | null | undefined {
|
|
353
353
|
const siblings = this._containers.top();
|
|
354
354
|
if (siblings && Array.isArray(siblings)) {
|
|
355
355
|
// top index is a number because top container is an array
|
|
356
|
-
const index =
|
|
356
|
+
const index = this._indexes.top() as number;
|
|
357
357
|
if (siblings.length > index + 1) {
|
|
358
358
|
return siblings.splice(index + 1, 1)[0];
|
|
359
359
|
}
|
|
@@ -372,7 +372,7 @@ export class TraversalState {
|
|
|
372
372
|
* This method throws an error if you attempt to replace the root node of
|
|
373
373
|
* the tree.
|
|
374
374
|
*/
|
|
375
|
-
replace(...replacements:
|
|
375
|
+
replace(...replacements: ReadonlyArray<TreeNode>): void {
|
|
376
376
|
const parent = this._containers.top();
|
|
377
377
|
if (!parent) {
|
|
378
378
|
throw new PerseusError(
|
|
@@ -386,7 +386,7 @@ export class TraversalState {
|
|
|
386
386
|
// or object property. This is hard for Flow, so we have to do some
|
|
387
387
|
// unsafe casting and be careful when we use which cast version
|
|
388
388
|
if (Array.isArray(parent)) {
|
|
389
|
-
const index =
|
|
389
|
+
const index = this._indexes.top() as number;
|
|
390
390
|
// For an array parent we just splice the new nodes in
|
|
391
391
|
parent.splice(index, 1, ...replacements);
|
|
392
392
|
// Adjust the index to account for the changed array length.
|
|
@@ -394,7 +394,7 @@ export class TraversalState {
|
|
|
394
394
|
this._indexes.pop();
|
|
395
395
|
this._indexes.push(index + replacements.length - 1);
|
|
396
396
|
} else {
|
|
397
|
-
const property =
|
|
397
|
+
const property = this._indexes.top() as string;
|
|
398
398
|
// For an object parent we care how many new nodes there are
|
|
399
399
|
if (replacements.length === 0) {
|
|
400
400
|
// Deletion
|
|
@@ -417,7 +417,7 @@ export class TraversalState {
|
|
|
417
417
|
hasPreviousSibling(): boolean {
|
|
418
418
|
return (
|
|
419
419
|
Array.isArray(this._containers.top()) &&
|
|
420
|
-
(
|
|
420
|
+
(this._indexes.top() as number) > 0
|
|
421
421
|
);
|
|
422
422
|
}
|
|
423
423
|
|
|
@@ -441,7 +441,7 @@ export class TraversalState {
|
|
|
441
441
|
// Since we know that we have a previous sibling, we know that
|
|
442
442
|
// the value on top of the stack is a number, but we have to do
|
|
443
443
|
// this unsafe cast because Flow doesn't know that.
|
|
444
|
-
const index =
|
|
444
|
+
const index = this._indexes.pop() as number;
|
|
445
445
|
this._indexes.push(index - 1);
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -527,10 +527,9 @@ export class TraversalState {
|
|
|
527
527
|
* the TraversalState class simpler in a number of places.
|
|
528
528
|
*/
|
|
529
529
|
class Stack<T> {
|
|
530
|
-
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
531
530
|
stack: Array<T>;
|
|
532
531
|
|
|
533
|
-
constructor(array
|
|
532
|
+
constructor(array?: ReadonlyArray<T> | null) {
|
|
534
533
|
this.stack = array ? array.slice(0) : [];
|
|
535
534
|
}
|
|
536
535
|
|
|
@@ -541,6 +540,7 @@ class Stack<T> {
|
|
|
541
540
|
|
|
542
541
|
/** Pop a value off of the stack. */
|
|
543
542
|
pop(): T {
|
|
543
|
+
// @ts-expect-error [FEI-5003] - TS2322 - Type 'T | undefined' is not assignable to type 'T'.
|
|
544
544
|
return this.stack.pop();
|
|
545
545
|
}
|
|
546
546
|
|
|
@@ -550,7 +550,7 @@ class Stack<T> {
|
|
|
550
550
|
}
|
|
551
551
|
|
|
552
552
|
/** Return a copy of the stack as an array */
|
|
553
|
-
values():
|
|
553
|
+
values(): ReadonlyArray<T> {
|
|
554
554
|
return this.stack.slice(0);
|
|
555
555
|
}
|
|
556
556
|
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exclude": ["dist", "**/*.test.*", "**/*.testdata.*", "**/*.stories.*", "**/*.cypress.*"],
|
|
3
|
+
"extends": "../tsconfig-shared.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
},
|
|
8
|
+
"references": [
|
|
9
|
+
{"path": "../perseus-error"},
|
|
10
|
+
{"path": "../pure-markdown"},
|
|
11
|
+
]
|
|
12
|
+
}
|