@khanacademy/perseus-linter 0.0.0-PR862-20231207182234 → 0.0.0-PR971-20240207180432

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 (55) hide show
  1. package/dist/es/index.js +1 -1
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/package.json +7 -4
  6. package/.eslintrc.js +0 -12
  7. package/CHANGELOG.md +0 -148
  8. package/src/README.md +0 -41
  9. package/src/__tests__/matcher.test.ts +0 -498
  10. package/src/__tests__/rule.test.ts +0 -110
  11. package/src/__tests__/rules.test.ts +0 -548
  12. package/src/__tests__/selector-parser.test.ts +0 -51
  13. package/src/__tests__/tree-transformer.test.ts +0 -444
  14. package/src/index.ts +0 -281
  15. package/src/proptypes.ts +0 -19
  16. package/src/rule.ts +0 -419
  17. package/src/rules/absolute-url.ts +0 -23
  18. package/src/rules/all-rules.ts +0 -71
  19. package/src/rules/blockquoted-math.ts +0 -9
  20. package/src/rules/blockquoted-widget.ts +0 -9
  21. package/src/rules/double-spacing-after-terminal.ts +0 -11
  22. package/src/rules/extra-content-spacing.ts +0 -11
  23. package/src/rules/heading-level-1.ts +0 -13
  24. package/src/rules/heading-level-skip.ts +0 -19
  25. package/src/rules/heading-sentence-case.ts +0 -10
  26. package/src/rules/heading-title-case.ts +0 -68
  27. package/src/rules/image-alt-text.ts +0 -20
  28. package/src/rules/image-in-table.ts +0 -9
  29. package/src/rules/image-spaces-around-urls.ts +0 -34
  30. package/src/rules/image-widget.ts +0 -49
  31. package/src/rules/link-click-here.ts +0 -10
  32. package/src/rules/lint-utils.ts +0 -47
  33. package/src/rules/long-paragraph.ts +0 -13
  34. package/src/rules/math-adjacent.ts +0 -9
  35. package/src/rules/math-align-extra-break.ts +0 -10
  36. package/src/rules/math-align-linebreaks.ts +0 -42
  37. package/src/rules/math-empty.ts +0 -9
  38. package/src/rules/math-font-size.ts +0 -11
  39. package/src/rules/math-frac.ts +0 -9
  40. package/src/rules/math-nested.ts +0 -10
  41. package/src/rules/math-starts-with-space.ts +0 -11
  42. package/src/rules/math-text-empty.ts +0 -9
  43. package/src/rules/math-without-dollars.ts +0 -13
  44. package/src/rules/nested-lists.ts +0 -10
  45. package/src/rules/profanity.ts +0 -9
  46. package/src/rules/table-missing-cells.ts +0 -19
  47. package/src/rules/unbalanced-code-delimiters.ts +0 -13
  48. package/src/rules/unescaped-dollar.ts +0 -9
  49. package/src/rules/widget-in-table.ts +0 -9
  50. package/src/selector.ts +0 -504
  51. package/src/tree-transformer.ts +0 -583
  52. package/src/types.ts +0 -7
  53. package/src/version.ts +0 -10
  54. package/tsconfig-build.json +0 -12
  55. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,20 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "image-alt-text",
5
- severity: Rule.Severity.WARNING,
6
- selector: "image",
7
- lint: function (state, content, nodes, match) {
8
- const image = nodes[0];
9
- if (!image.alt || !image.alt.trim()) {
10
- return `Images should have alt text:
11
- for accessibility, all images should have alt text.
12
- Specify alt text inside square brackets after the !.`;
13
- }
14
- if (image.alt.length < 8) {
15
- return `Images should have alt text:
16
- for accessibility, all images should have descriptive alt text.
17
- This image's alt text is only ${image.alt.length} characters long.`;
18
- }
19
- },
20
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "image-in-table",
5
- severity: Rule.Severity.BULK_WARNING,
6
- selector: "table image",
7
- message: `Image in table:
8
- do not put images inside of tables.`,
9
- }) as Rule;
@@ -1,34 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "image-spaces-around-urls",
5
- severity: Rule.Severity.ERROR,
6
- selector: "image",
7
- lint: function (state, content, nodes, match, context) {
8
- const image = nodes[0];
9
- const url = image.target;
10
-
11
- // The markdown parser strips leading and trailing spaces for us,
12
- // but they're still a problem for our translation process, so
13
- // we need to go check for them in the unparsed source string
14
- // if we have it.
15
- if (context && context.content) {
16
- // Find the url in the original content and make sure that the
17
- // character before is '(' and the character after is ')'
18
- const index = context.content.indexOf(url);
19
- if (index === -1) {
20
- // It is not an error if we didn't find it.
21
- return;
22
- }
23
-
24
- if (
25
- context.content[index - 1] !== "(" ||
26
- context.content[index + url.length] !== ")"
27
- ) {
28
- return `Whitespace before or after image url:
29
- For images, don't include any space or newlines after '(' or before ')'.
30
- Whitespace in image URLs causes translation difficulties.`;
31
- }
32
- }
33
- },
34
- }) as Rule;
@@ -1,49 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- // Normally we have one rule per file. But since our selector class
4
- // can't match specific widget types directly, this rule implements
5
- // a number of image widget related rules in one place. This should
6
- // slightly increase efficiency, but it means that if there is more
7
- // than one problem with an image widget, the user will only see one
8
- // problem at a time.
9
- export default Rule.makeRule({
10
- name: "image-widget",
11
- severity: Rule.Severity.WARNING,
12
- selector: "widget",
13
- lint: function (state, content, nodes, match, context) {
14
- // This rule only looks at image widgets
15
- if (state.currentNode().widgetType !== "image") {
16
- return;
17
- }
18
-
19
- // If it can't find a definition for the widget it does nothing
20
- const widget =
21
- context &&
22
- context.widgets &&
23
- context.widgets[state.currentNode().id];
24
- if (!widget) {
25
- return;
26
- }
27
-
28
- // Make sure there is alt text
29
- const alt = widget.options.alt;
30
- if (!alt) {
31
- return `Images should have alt text:
32
- for accessibility, all images should have a text description.
33
- Add a description in the "Alt Text" box of the image widget.`;
34
- }
35
-
36
- // Make sure the alt text it is not trivial
37
- if (alt.trim().length < 8) {
38
- return `Images should have alt text:
39
- for accessibility, all images should have descriptive alt text.
40
- This image's alt text is only ${alt.trim().length} characters long.`;
41
- }
42
-
43
- // Make sure there is no math in the caption
44
- if (widget.options.caption && widget.options.caption.match(/[^\\]\$/)) {
45
- return `No math in image captions:
46
- Don't include math expressions in image captions.`;
47
- }
48
- },
49
- }) as Rule;
@@ -1,10 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "link-click-here",
5
- severity: Rule.Severity.WARNING,
6
- selector: "link",
7
- pattern: /click here/i,
8
- message: `Inappropriate link text:
9
- Do not use the words "click here" in links.`,
10
- }) as Rule;
@@ -1,47 +0,0 @@
1
- /* eslint-disable no-useless-escape */
2
- // Return the portion of a URL between // and /. This is the authority
3
- // portion which is usually just the hostname, but may also include
4
- // a username, password or port. We don't strip those things out because
5
- // we typically want to reject any URL that includes them
6
- const HOSTNAME = /\/\/([^\/]+)/;
7
-
8
- // Return the hostname of the URL, with any "www." prefix removed.
9
- // If this is a relative URL with no hostname, return an empty string.
10
- export function getHostname(url: string): string {
11
- if (!url) {
12
- return "";
13
- }
14
- const match = url.match(HOSTNAME);
15
- return match ? match[1] : "";
16
- }
17
-
18
- // This list of domains that count as internal domains is from
19
- // webapp/content/models.py and webapp/url_util.py
20
- const internalDomains = {
21
- "khanacademy.org": true,
22
- "www.khanacademy.org": true,
23
- "kasandbox.org": true,
24
- "fastly.kastatic.org": true,
25
- "cdn.kastatic.org": true, // This isn't a link to cdn.kastatic.org
26
- "ka-youtube-converted.storage.googleapis.com": true,
27
- "KA-share.s3.amazonaws.com": true,
28
- "ka-article-iframes.s3.amazonaws.com": true,
29
- "ka-cs-algorithms.s3.amazonaws.com": true,
30
- "ka-cs-challenge-images.s3.amazonaws.com": true,
31
- "ka-cs-scratchpad-audio.s3.amazonaws.com": true,
32
- "ka-exercise-screenshots.s3.amazonaws.com": true,
33
- "ka-exercise-screenshots-2.s3.amazonaws.com": true,
34
- "ka-exercise-screenshots-3.s3.amazonaws.com": true,
35
- "ka-learnstorm.s3.amazonaws.com": true,
36
- "ka-mobile.s3.amazonaws.com": true,
37
- "ka-perseus-graphie.s3.amazonaws.com": true,
38
- "ka-perseus-images.s3.amazonaws.com": true,
39
- } as const;
40
-
41
- // Returns true if this URL is relative, or if it is an absolute
42
- // URL with one of the domains listed above as its hostname.
43
- export function isInternalURL(url: string): boolean {
44
- const hostname = getHostname(url);
45
- // eslint-disable-next-line no-prototype-builtins
46
- return hostname === "" || internalDomains.hasOwnProperty(hostname);
47
- }
@@ -1,13 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "long-paragraph",
5
- severity: Rule.Severity.GUIDELINE,
6
- selector: "paragraph",
7
- pattern: /^.{501,}/,
8
- lint: function (state, content, nodes, match) {
9
- return `Paragraph too long:
10
- This paragraph is ${content.length} characters long.
11
- Shorten it to 500 characters or fewer.`;
12
- },
13
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-adjacent",
5
- severity: Rule.Severity.WARNING,
6
- selector: "blockMath+blockMath",
7
- message: `Adjacent math blocks:
8
- combine the blocks between \\begin{align} and \\end{align}`,
9
- }) as Rule;
@@ -1,10 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-align-extra-break",
5
- severity: Rule.Severity.WARNING,
6
- selector: "blockMath",
7
- pattern: /(\\{2,})\s*\\end{align}/,
8
- message: `Extra space at end of block:
9
- Don't end an align block with backslashes`,
10
- }) as Rule;
@@ -1,42 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-align-linebreaks",
5
- severity: Rule.Severity.WARNING,
6
- selector: "blockMath",
7
- // Match any align block with double backslashes in it
8
- // Use [\s\S]* instead of .* so we match newlines as well.
9
- pattern: /\\begin{align}[\s\S]*\\\\[\s\S]+\\end{align}/,
10
- // Look for double backslashes and ensure that they are
11
- // followed by optional space and another pair of backslashes.
12
- // Note that this rule can't know where line breaks belong so
13
- // it can't tell whether backslashes are completely missing. It just
14
- // enforces that you don't have the wrong number of pairs of backslashes.
15
- lint: function (state, content, nodes, match) {
16
- let text = match[0];
17
- while (text.length) {
18
- const index = text.indexOf("\\\\");
19
- if (index === -1) {
20
- // No more backslash pairs, so we found no lint
21
- return null;
22
- }
23
- text = text.substring(index + 2);
24
-
25
- // Now we expect to find optional spaces, another pair of
26
- // backslashes, and more optional spaces not followed immediately
27
- // by another pair of backslashes.
28
- const nextpair = text.match(/^\s*\\\\\s*(?!\\\\)/);
29
-
30
- // If that does not match then we either have too few or too
31
- // many pairs of backslashes.
32
- if (!nextpair) {
33
- return "Use four backslashes between lines of an align block";
34
- }
35
-
36
- // If it did match, then, shorten the string and continue looping
37
- // (because a single align block may have multiple lines that
38
- // all must be separated by two sets of double backslashes).
39
- text = text.substring(nextpair[0].length);
40
- }
41
- },
42
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-empty",
5
- severity: Rule.Severity.WARNING,
6
- selector: "math, blockMath",
7
- pattern: /^$/,
8
- message: "Empty math: don't use $$ in your markdown.",
9
- }) as Rule;
@@ -1,11 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-font-size",
5
- severity: Rule.Severity.GUIDELINE,
6
- selector: "math, blockMath",
7
- pattern:
8
- /\\(tiny|Tiny|small|large|Large|LARGE|huge|Huge|scriptsize|normalsize)\s*{/,
9
- message: `Math font size:
10
- Don't change the default font size with \\Large{} or similar commands`,
11
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-frac",
5
- severity: Rule.Severity.GUIDELINE,
6
- selector: "math, blockMath",
7
- pattern: /\\frac[ {]/,
8
- message: "Use \\dfrac instead of \\frac in your math expressions.",
9
- }) as Rule;
@@ -1,10 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-nested",
5
- severity: Rule.Severity.ERROR,
6
- selector: "math, blockMath",
7
- pattern: /\\text{[^$}]*\$[^$}]*\$[^}]*}/,
8
- message: `Nested math:
9
- Don't nest math expressions inside \\text{} blocks`,
10
- }) as Rule;
@@ -1,11 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-starts-with-space",
5
- severity: Rule.Severity.GUIDELINE,
6
- selector: "math, blockMath",
7
- pattern: /^\s*(~|\\qquad|\\quad|\\,|\\;|\\:|\\ |\\!|\\enspace|\\phantom)/,
8
- message: `Math starts with space:
9
- math should not be indented. Do not begin math expressions with
10
- LaTeX space commands like ~, \\;, \\quad, or \\phantom`,
11
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "math-text-empty",
5
- severity: Rule.Severity.WARNING,
6
- selector: "math, blockMath",
7
- pattern: /\\text{\s*}/,
8
- message: "Empty \\text{} block in math expression",
9
- }) as Rule;
@@ -1,13 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- // Because no selector is specified, this rule only applies to text nodes.
4
- // Math and code hold their content directly and do not have text nodes
5
- // beneath them (unlike the HTML DOM) so this rule automatically does not
6
- // apply inside $$ or ``.
7
- export default Rule.makeRule({
8
- name: "math-without-dollars",
9
- severity: Rule.Severity.GUIDELINE,
10
- pattern: /\\\w+{[^}]*}|{|}/,
11
- message: `This looks like LaTeX:
12
- did you mean to put it inside dollar signs?`,
13
- }) as Rule;
@@ -1,10 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "nested-lists",
5
- severity: Rule.Severity.WARNING,
6
- selector: "list list",
7
- message: `Nested lists:
8
- nested lists are hard to read on mobile devices;
9
- do not use additional indentation.`,
10
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "profanity",
5
- // This list could obviously be expanded a lot, but I figured we
6
- // could start with https://en.wikipedia.org/wiki/Seven_dirty_words
7
- pattern: /\b(shit|piss|fuck|cunt|cocksucker|motherfucker|tits)\b/i,
8
- message: "Avoid profanity",
9
- }) as Rule;
@@ -1,19 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "table-missing-cells",
5
- severity: Rule.Severity.WARNING,
6
- selector: "table",
7
- lint: function (state, content, nodes, match) {
8
- const table = nodes[0];
9
- const headerLength = table.header.length;
10
- const rowLengths = table.cells.map((r) => r.length);
11
- for (let r = 0; r < rowLengths.length; r++) {
12
- if (rowLengths[r] !== headerLength) {
13
- return `Table rows don't match header:
14
- The table header has ${headerLength} cells, but
15
- Row ${r + 1} has ${rowLengths[r]} cells.`;
16
- }
17
- }
18
- },
19
- }) as Rule;
@@ -1,13 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- // Because no selector is specified, this rule only applies to text nodes.
4
- // Math and code hold their content directly and do not have text nodes
5
- // beneath them (unlike the HTML DOM) so this rule automatically does not
6
- // apply inside $$ or ``.
7
- export default Rule.makeRule({
8
- name: "unbalanced-code-delimiters",
9
- severity: Rule.Severity.ERROR,
10
- pattern: /[`~]+/,
11
- message: `Unbalanced code delimiters:
12
- code blocks should begin and end with the same type and number of delimiters`,
13
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "unescaped-dollar",
5
- severity: Rule.Severity.ERROR,
6
- selector: "unescapedDollar",
7
- message: `Unescaped dollar sign:
8
- Dollar signs must appear in pairs or be escaped as \\$`,
9
- }) as Rule;
@@ -1,9 +0,0 @@
1
- import Rule from "../rule";
2
-
3
- export default Rule.makeRule({
4
- name: "widget-in-table",
5
- severity: Rule.Severity.BULK_WARNING,
6
- selector: "table widget",
7
- message: `Widget in table:
8
- do not put widgets inside of tables.`,
9
- }) as Rule;