@starci/eslint-canon-fe 1.0.1 → 1.2.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/typography.mjs CHANGED
@@ -1,83 +1,83 @@
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
+ /** 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"]))