@starci/eslint-canon-fe 4.0.0 → 5.1.2

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 (52) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +34 -16
  3. package/brand-values.mjs +174 -0
  4. package/class-names.mjs +1 -1
  5. package/client-boundary.mjs +199 -0
  6. package/comments.mjs +144 -190
  7. package/docs/brand-values.md +30 -0
  8. package/docs/client-boundary.md +99 -0
  9. package/docs/e2e-shape.md +161 -0
  10. package/docs/env-owner.md +52 -0
  11. package/docs/formatting.md +30 -0
  12. package/docs/hooks-folder.md +29 -0
  13. package/docs/hygiene.md +111 -0
  14. package/docs/lint-escape-hatch.md +32 -0
  15. package/docs/lists.md +74 -0
  16. package/docs/native-controls.md +73 -0
  17. package/docs/next-conventions.md +206 -0
  18. package/docs/size-and-state-budget.md +73 -0
  19. package/docs/spec-quality.md +139 -0
  20. package/docs/translation.md +50 -0
  21. package/docs/transport.md +168 -0
  22. package/docs/type-safety.md +95 -0
  23. package/e2e-shape.mjs +348 -0
  24. package/env-owner.mjs +134 -0
  25. package/file-layout.mjs +1 -2
  26. package/formatting.mjs +106 -0
  27. package/hooks-folder.mjs +119 -0
  28. package/hygiene.mjs +235 -0
  29. package/icon.mjs +210 -211
  30. package/index.mjs +290 -131
  31. package/lib/ast.mjs +94 -0
  32. package/lib/path.mjs +2 -0
  33. package/lib/scope.mjs +42 -0
  34. package/lib/why.mjs +304 -0
  35. package/lint-adoption.mjs +15 -52
  36. package/lint-escape-hatch.mjs +91 -56
  37. package/lists.mjs +197 -0
  38. package/loading.mjs +163 -164
  39. package/naming.mjs +143 -143
  40. package/native-controls.mjs +112 -0
  41. package/next-conventions.mjs +346 -0
  42. package/package.json +8 -4
  43. package/served-locale.mjs +160 -160
  44. package/shape-slot.mjs +1 -2
  45. package/size-and-state-budget.mjs +196 -0
  46. package/spec-quality.mjs +239 -0
  47. package/the-split.mjs +133 -134
  48. package/tokens.mjs +267 -268
  49. package/translation.mjs +262 -122
  50. package/transport.mjs +404 -0
  51. package/type-safety.mjs +156 -74
  52. package/typography.mjs +82 -83
package/typography.mjs CHANGED
@@ -1,83 +1,82 @@
1
- /**
2
- * The rule that holds `typography.md`.
3
- *
4
- * MOST OF THIS LAW IS A UNION, and what is left is one shape a type cannot see: a heading TAG
5
- * written by hand. The level union stops a fifth step being asked for; nothing in the type system
6
- * notices an `<h2>` in a file that never mentions the heading component at all.
7
- *
8
- * The rule's own twin lives in `tokens.mjs`: that one catches a heading assembled out of a large
9
- * size and a heavy weight on some other element. Between them they cover both ways a heading gets
10
- * hand-rolled - with the right tag and the wrong look, or the right look and no tag.
11
- */
12
-
13
- /** Forward-slash form of a filename, so Windows paths compare like every other path. */
14
- const normalizePath = (filename) => String(filename || "").replace(/\\/g, "/")
15
-
16
- /** Product source lives under `src/`; tooling and config are out of scope. */
17
- const isSourceFile = (filename) => normalizePath(filename).includes("/src/")
18
-
19
- /** A twin test may build heading markup by hand to assert against it. */
20
- const isTestFile = (filename) => /\.(?:test|spec)\.(?:ts|tsx)$/.test(normalizePath(filename))
21
-
22
- /** The one component that owns the tag and the size together, and therefore may write the tag. */
23
- const isHeadingComponent = (filename) => normalizePath(filename).includes("/src/components/leaves/Heading/")
24
-
25
- /** The tags a document outline is built from. */
26
- const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"])
27
-
28
- /** The deepest level the component draws; below this a page has nested too far. */
29
- const DEEPEST_LEVEL = 4
30
-
31
- /** Intrinsic (lowercase) tag name, or null for a component. */
32
- const tagName = (opening) => {
33
- const name = opening && opening.name
34
- if (!name || name.type !== "JSXIdentifier") return null
35
- return name.name === name.name.toLowerCase() ? name.name : null
36
- }
37
-
38
- // -- TYPESET-1 · TYPESET-2 -------------------------------------------------------------------------------
39
-
40
- /** A heading comes from the component that owns its tag and its size as one decision. */
41
- export const noHeadingTagOutsideHeadingComponent = {
42
- meta: {
43
- type: "problem",
44
- docs: { description: "Heading tags are written by the heading component, from a level." },
45
- schema: [],
46
- messages: {
47
- tag:
48
- "`<{{tag}}>` written here throws away the two facts the heading component exists to keep together: the tag a screen reader builds the document outline from, and the size a reader sees. Set separately they drift, and a screen's third-largest text becomes its first heading. Render the heading component with a level of {{level}}; one prop decides both, so they cannot disagree.",
49
- tooDeep:
50
- "`<{{tag}}>` is deeper than the scale goes: levels stop at {{deepest}}, because a fifth means the page has nested further than a reader can hold. That is a structure problem wearing a styling problem's clothes - flatten the section, then render the title with a level the scale has.",
51
- },
52
- },
53
- create(context) {
54
- const file = normalizePath(context.filename || context.getFilename())
55
- if (!isSourceFile(file) || isTestFile(file) || isHeadingComponent(file)) return {}
56
- return {
57
- JSXOpeningElement(node) {
58
- const tag = tagName(node)
59
- if (!tag || !HEADING_TAGS.has(tag)) return
60
- const level = Number(tag.slice(1))
61
- if (level > DEEPEST_LEVEL) {
62
- context.report({ node, messageId: "tooDeep", data: { tag, deepest: DEEPEST_LEVEL } })
63
- return
64
- }
65
- context.report({ node, messageId: "tag", data: { tag, level } })
66
- },
67
- }
68
- },
69
- }
70
-
71
- /** The rules this law contributes to the plugin. */
72
- export const rules = {
73
- "no-heading-tag-outside-heading-component": noHeadingTagOutsideHeadingComponent,
74
- }
75
-
76
- /**
77
- * The level this law asks for, as the plugin's own opinion.
78
- *
79
- * Exact: a tag name in a file that is not the component that owns it. A repository adopting this
80
- * with history should expect the deeper levels to report a structure problem rather than a styling
81
- * one, and those take longer to fix than the message suggests.
82
- */
83
- export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-fe/${name}`, "error"]))
1
+ /**
2
+ * The rule that holds `typography.md`.
3
+ *
4
+ * MOST OF THIS LAW IS A UNION, and what is left is one shape a type cannot see: a heading TAG
5
+ * written by hand. The level union stops a fifth step being asked for; nothing in the type system
6
+ * notices an `<h2>` in a file that never mentions the heading component at all.
7
+ *
8
+ * The rule's own twin lives in `tokens.mjs`: that one catches a heading assembled out of a large
9
+ * size and a heavy weight on some other element. Between them they cover both ways a heading gets
10
+ * hand-rolled - with the right tag and the wrong look, or the right look and no tag.
11
+ */
12
+
13
+ import { normalizePath } from "./lib/path.mjs"
14
+
15
+ /** Product source lives under `src/`; tooling and config are out of scope. */
16
+ const isSourceFile = (filename) => normalizePath(filename).includes("/src/")
17
+
18
+ /** A twin test may build heading markup by hand to assert against it. */
19
+ const isTestFile = (filename) => /\.(?:test|spec)\.(?:ts|tsx)$/.test(normalizePath(filename))
20
+
21
+ /** The one component that owns the tag and the size together, and therefore may write the tag. */
22
+ const isHeadingComponent = (filename) => normalizePath(filename).includes("/src/components/leaves/Heading/")
23
+
24
+ /** The tags a document outline is built from. */
25
+ const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"])
26
+
27
+ /** The deepest level the component draws; below this a page has nested too far. */
28
+ const DEEPEST_LEVEL = 4
29
+
30
+ /** Intrinsic (lowercase) tag name, or null for a component. */
31
+ const tagName = (opening) => {
32
+ const name = opening && opening.name
33
+ if (!name || name.type !== "JSXIdentifier") return null
34
+ return name.name === name.name.toLowerCase() ? name.name : null
35
+ }
36
+
37
+ // -- TYPESET-1 · TYPESET-2 -------------------------------------------------------------------------------
38
+
39
+ /** A heading comes from the component that owns its tag and its size as one decision. */
40
+ export const noHeadingTagOutsideHeadingComponent = {
41
+ meta: {
42
+ type: "problem",
43
+ docs: { description: "Heading tags are written by the heading component, from a level." },
44
+ schema: [],
45
+ messages: {
46
+ tag:
47
+ "`<{{tag}}>` written here throws away the two facts the heading component exists to keep together: the tag a screen reader builds the document outline from, and the size a reader sees. Set separately they drift, and a screen's third-largest text becomes its first heading. Render the heading component with a level of {{level}}; one prop decides both, so they cannot disagree.",
48
+ tooDeep:
49
+ "`<{{tag}}>` is deeper than the scale goes: levels stop at {{deepest}}, because a fifth means the page has nested further than a reader can hold. That is a structure problem wearing a styling problem's clothes - flatten the section, then render the title with a level the scale has.",
50
+ },
51
+ },
52
+ create(context) {
53
+ const file = normalizePath(context.filename || context.getFilename())
54
+ if (!isSourceFile(file) || isTestFile(file) || isHeadingComponent(file)) return {}
55
+ return {
56
+ JSXOpeningElement(node) {
57
+ const tag = tagName(node)
58
+ if (!tag || !HEADING_TAGS.has(tag)) return
59
+ const level = Number(tag.slice(1))
60
+ if (level > DEEPEST_LEVEL) {
61
+ context.report({ node, messageId: "tooDeep", data: { tag, deepest: DEEPEST_LEVEL } })
62
+ return
63
+ }
64
+ context.report({ node, messageId: "tag", data: { tag, level } })
65
+ },
66
+ }
67
+ },
68
+ }
69
+
70
+ /** The rules this law contributes to the plugin. */
71
+ export const rules = {
72
+ "no-heading-tag-outside-heading-component": noHeadingTagOutsideHeadingComponent,
73
+ }
74
+
75
+ /**
76
+ * The level this law asks for, as the plugin's own opinion.
77
+ *
78
+ * Exact: a tag name in a file that is not the component that owns it. A repository adopting this
79
+ * with history should expect the deeper levels to report a structure problem rather than a styling
80
+ * one, and those take longer to fix than the message suggests.
81
+ */
82
+ export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-fe/${name}`, "error"]))