pasika 0.3.3 → 0.3.4
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/README.md +13 -17
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +4999 -103
- package/dist/eslint/pasika/index.d.ts +165 -53
- package/dist/eslint/pasika/index.js +4424 -89
- package/package.json +3 -3
- package/dist/cli/output.d.ts +0 -3
- package/dist/cli/output.js +0 -11
- package/dist/enforcement/coverage.d.ts +0 -55
- package/dist/enforcement/coverage.js +0 -239
- package/dist/enforcement/doctor.d.ts +0 -16
- package/dist/enforcement/doctor.js +0 -249
- package/dist/enforcement/normalize.d.ts +0 -11
- package/dist/enforcement/normalize.js +0 -21
- package/dist/enforcement/parse-docs.d.ts +0 -58
- package/dist/enforcement/parse-docs.js +0 -98
- package/dist/enforcement/types.d.ts +0 -54
- package/dist/enforcement/types.js +0 -50
- package/dist/eslint/pasika/ast-types.d.ts +0 -122
- package/dist/eslint/pasika/ast-types.js +0 -1
- package/dist/eslint/pasika/project/ccf.d.ts +0 -48
- package/dist/eslint/pasika/project/ccf.js +0 -119
- package/dist/eslint/pasika/project/index.d.ts +0 -21
- package/dist/eslint/pasika/project/index.js +0 -139
- package/dist/eslint/pasika/project/parse-module.d.ts +0 -27
- package/dist/eslint/pasika/project/parse-module.js +0 -128
- package/dist/eslint/pasika/rules/application-structure.d.ts +0 -2
- package/dist/eslint/pasika/rules/application-structure.js +0 -152
- package/dist/eslint/pasika/rules/component-conventions.d.ts +0 -16
- package/dist/eslint/pasika/rules/component-conventions.js +0 -145
- package/dist/eslint/pasika/rules/component-nesting.d.ts +0 -12
- package/dist/eslint/pasika/rules/component-nesting.js +0 -69
- package/dist/eslint/pasika/rules/component-placement.d.ts +0 -11
- package/dist/eslint/pasika/rules/component-placement.js +0 -75
- package/dist/eslint/pasika/rules/config-extraction.d.ts +0 -14
- package/dist/eslint/pasika/rules/config-extraction.js +0 -74
- package/dist/eslint/pasika/rules/cross-feature-import.d.ts +0 -2
- package/dist/eslint/pasika/rules/cross-feature-import.js +0 -75
- package/dist/eslint/pasika/rules/css/apply-usage.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/apply-usage.js +0 -45
- package/dist/eslint/pasika/rules/css/base-layer-pair.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/base-layer-pair.js +0 -49
- package/dist/eslint/pasika/rules/css/css-variable-naming.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/css-variable-naming.js +0 -39
- package/dist/eslint/pasika/rules/css/custom-utility-apply.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/custom-utility-apply.js +0 -52
- package/dist/eslint/pasika/rules/css/global-css-location.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/global-css-location.js +0 -42
- package/dist/eslint/pasika/rules/css/helpers.d.ts +0 -29
- package/dist/eslint/pasika/rules/css/helpers.js +0 -93
- package/dist/eslint/pasika/rules/css/index.d.ts +0 -13
- package/dist/eslint/pasika/rules/css/index.js +0 -25
- package/dist/eslint/pasika/rules/css/root-variables.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/root-variables.js +0 -53
- package/dist/eslint/pasika/rules/css/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/rule-tester.js +0 -22
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.js +0 -76
- package/dist/eslint/pasika/rules/css/surface-utility.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/surface-utility.js +0 -41
- package/dist/eslint/pasika/rules/css/theme-reset.d.ts +0 -9
- package/dist/eslint/pasika/rules/css/theme-reset.js +0 -36
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.js +0 -53
- package/dist/eslint/pasika/rules/cva-appearance-props.d.ts +0 -10
- package/dist/eslint/pasika/rules/cva-appearance-props.js +0 -77
- package/dist/eslint/pasika/rules/cva-boolean-variants.d.ts +0 -11
- package/dist/eslint/pasika/rules/cva-boolean-variants.js +0 -97
- package/dist/eslint/pasika/rules/data-testid-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/data-testid-case.js +0 -76
- package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-barrel-exports.js +0 -78
- package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +0 -21
- package/dist/eslint/pasika/rules/enforce-cn-merge.js +0 -86
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +0 -86
- package/dist/eslint/pasika/rules/filename-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/filename-case.js +0 -123
- package/dist/eslint/pasika/rules/hook-complexity.d.ts +0 -12
- package/dist/eslint/pasika/rules/hook-complexity.js +0 -114
- package/dist/eslint/pasika/rules/hook-extraction.d.ts +0 -11
- package/dist/eslint/pasika/rules/hook-extraction.js +0 -55
- package/dist/eslint/pasika/rules/import-boundaries.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-boundaries.js +0 -167
- package/dist/eslint/pasika/rules/import-through-index.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-through-index.js +0 -52
- package/dist/eslint/pasika/rules/interactive-component.d.ts +0 -10
- package/dist/eslint/pasika/rules/interactive-component.js +0 -76
- package/dist/eslint/pasika/rules/json/index.d.ts +0 -5
- package/dist/eslint/pasika/rules/json/index.js +0 -9
- package/dist/eslint/pasika/rules/json/no-cache-flag.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-cache-flag.js +0 -45
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.js +0 -41
- package/dist/eslint/pasika/rules/json/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/json/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/jsx-hygiene.d.ts +0 -9
- package/dist/eslint/pasika/rules/jsx-hygiene.js +0 -108
- package/dist/eslint/pasika/rules/locale-dotted-path.d.ts +0 -10
- package/dist/eslint/pasika/rules/locale-dotted-path.js +0 -65
- package/dist/eslint/pasika/rules/locale-placement.d.ts +0 -13
- package/dist/eslint/pasika/rules/locale-placement.js +0 -163
- package/dist/eslint/pasika/rules/locales-location.d.ts +0 -11
- package/dist/eslint/pasika/rules/locales-location.js +0 -56
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.js +0 -29
- package/dist/eslint/pasika/rules/md/example-heading-description.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/example-heading-description.js +0 -30
- package/dist/eslint/pasika/rules/md/glossary-term-linking.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/glossary-term-linking.js +0 -99
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.js +0 -46
- package/dist/eslint/pasika/rules/md/guide-link-anchors.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-link-anchors.js +0 -50
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.js +0 -51
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.js +0 -26
- package/dist/eslint/pasika/rules/md/guide-step-single-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-link.js +0 -48
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.js +0 -42
- package/dist/eslint/pasika/rules/md/helpers.d.ts +0 -25
- package/dist/eslint/pasika/rules/md/helpers.js +0 -37
- package/dist/eslint/pasika/rules/md/index.d.ts +0 -27
- package/dist/eslint/pasika/rules/md/index.js +0 -53
- package/dist/eslint/pasika/rules/md/no-cross-document-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-cross-document-link.js +0 -36
- package/dist/eslint/pasika/rules/md/no-nested-how-to.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-nested-how-to.js +0 -39
- package/dist/eslint/pasika/rules/md/no-template-prompt.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-template-prompt.js +0 -22
- package/dist/eslint/pasika/rules/md/overview-length.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-length.js +0 -47
- package/dist/eslint/pasika/rules/md/overview-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-present.js +0 -41
- package/dist/eslint/pasika/rules/md/policy-no-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-no-examples.js +0 -28
- package/dist/eslint/pasika/rules/md/policy-single-document.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-single-document.js +0 -34
- package/dist/eslint/pasika/rules/md/policy-subject-headings.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-subject-headings.js +0 -58
- package/dist/eslint/pasika/rules/md/project-index.d.ts +0 -17
- package/dist/eslint/pasika/rules/md/project-index.js +0 -67
- package/dist/eslint/pasika/rules/md/reference-block-headings.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-block-headings.js +0 -31
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.js +0 -26
- package/dist/eslint/pasika/rules/md/requirement-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/requirement-present.js +0 -41
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.js +0 -46
- package/dist/eslint/pasika/rules/md/rule-paired-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rule-paired-examples.js +0 -36
- package/dist/eslint/pasika/rules/md/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/md/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/md/support-document-placement.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/support-document-placement.js +0 -36
- package/dist/eslint/pasika/rules/md/title-matches-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/title-matches-file-name.js +0 -46
- package/dist/eslint/pasika/rules/named-exports.d.ts +0 -2
- package/dist/eslint/pasika/rules/named-exports.js +0 -49
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +0 -97
- package/dist/eslint/pasika/rules/no-eslint-disable.d.ts +0 -10
- package/dist/eslint/pasika/rules/no-eslint-disable.js +0 -33
- package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-mixed-concerns.js +0 -71
- package/dist/eslint/pasika/rules/no-util-barrel.d.ts +0 -2
- package/dist/eslint/pasika/rules/no-util-barrel.js +0 -48
- package/dist/eslint/pasika/rules/pure-function-extract.d.ts +0 -9
- package/dist/eslint/pasika/rules/pure-function-extract.js +0 -92
- package/dist/eslint/pasika/rules/shared-style-dedup.d.ts +0 -13
- package/dist/eslint/pasika/rules/shared-style-dedup.js +0 -91
- package/dist/eslint/pasika/rules/stay-flat.d.ts +0 -12
- package/dist/eslint/pasika/rules/stay-flat.js +0 -81
- package/dist/eslint/pasika/rules/support-file-placement.d.ts +0 -15
- package/dist/eslint/pasika/rules/support-file-placement.js +0 -70
- package/dist/eslint/pasika/rules/support-folder-shape.d.ts +0 -2
- package/dist/eslint/pasika/rules/support-folder-shape.js +0 -59
- package/dist/eslint/pasika/rules/type-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/type-extraction.js +0 -85
- package/dist/eslint/pasika/rules/ui-state.d.ts +0 -9
- package/dist/eslint/pasika/rules/ui-state.js +0 -99
- package/dist/eslint/pasika/rules/util-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/util-file-name.js +0 -48
- package/dist/eslint/pasika/rules/value-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/value-extraction.js +0 -48
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/hook-complexity
|
|
3
|
-
*
|
|
4
|
-
* - A custom hook with exactly one consumer MUST be extracted when it contains
|
|
5
|
-
* two or more imperative categories.
|
|
6
|
-
* - A custom hook with one consumer that contains fewer than two imperative
|
|
7
|
-
* categories MUST stay inline in its consumer file.
|
|
8
|
-
*
|
|
9
|
-
* @see docs/code-organization-guide/rules/hook-extraction-rule.md
|
|
10
|
-
*/
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import ts from "typescript";
|
|
13
|
-
const REACT_HOOKS = new Set([
|
|
14
|
-
"useState",
|
|
15
|
-
"useEffect",
|
|
16
|
-
"useContext",
|
|
17
|
-
"useReducer",
|
|
18
|
-
"useCallback",
|
|
19
|
-
"useMemo",
|
|
20
|
-
"useRef",
|
|
21
|
-
"useLayoutEffect",
|
|
22
|
-
"useImperativeHandle",
|
|
23
|
-
"useDebugValue",
|
|
24
|
-
"useDeferredValue",
|
|
25
|
-
"useTransition",
|
|
26
|
-
"useId",
|
|
27
|
-
"useSyncExternalStore",
|
|
28
|
-
"useInsertionEffect",
|
|
29
|
-
]);
|
|
30
|
-
function isHookName(name) {
|
|
31
|
-
return /^use[A-Z]/.test(name);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Counts the distinct React hook categories called anywhere inside a hook body.
|
|
35
|
-
* Re-parses just the body's source slice with the TypeScript compiler so the
|
|
36
|
-
* walk stays fully typed instead of unrolling ESTree unions by hand.
|
|
37
|
-
*/
|
|
38
|
-
function countImperativeCategories(body, sourceText) {
|
|
39
|
-
const start = body.range?.[0] ?? 0;
|
|
40
|
-
const end = body.range?.[1] ?? sourceText.length;
|
|
41
|
-
const sourceFile = ts.createSourceFile("hook.ts", sourceText.slice(start, end), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
42
|
-
const categories = new Set();
|
|
43
|
-
const visit = (node) => {
|
|
44
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && REACT_HOOKS.has(node.expression.text)) {
|
|
45
|
-
categories.add(node.expression.text);
|
|
46
|
-
}
|
|
47
|
-
ts.forEachChild(node, visit);
|
|
48
|
-
};
|
|
49
|
-
visit(sourceFile);
|
|
50
|
-
return categories.size;
|
|
51
|
-
}
|
|
52
|
-
export const hookComplexityRule = {
|
|
53
|
-
meta: {
|
|
54
|
-
schema: [],
|
|
55
|
-
type: "problem",
|
|
56
|
-
docs: {
|
|
57
|
-
description: "Require extraction of complex single-consumer hooks and inline of simple ones.",
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
create(context) {
|
|
61
|
-
const filename = context.filename;
|
|
62
|
-
const sourceRoot = path.resolve("src");
|
|
63
|
-
const relative = path.relative(sourceRoot, filename);
|
|
64
|
-
if (relative.startsWith(".."))
|
|
65
|
-
return {};
|
|
66
|
-
const segments = relative.split(path.sep);
|
|
67
|
-
const sourceText = context.sourceCode.text;
|
|
68
|
-
function checkHook(node, name, body, exported) {
|
|
69
|
-
if (!exported)
|
|
70
|
-
return;
|
|
71
|
-
if (!name || !isHookName(name))
|
|
72
|
-
return;
|
|
73
|
-
if (!body)
|
|
74
|
-
return;
|
|
75
|
-
const imperativeCount = countImperativeCategories(body, sourceText);
|
|
76
|
-
const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : undefined;
|
|
77
|
-
const inSupportFolder = parentFolder === "hooks";
|
|
78
|
-
if (imperativeCount >= 2 && !inSupportFolder) {
|
|
79
|
-
context.report({
|
|
80
|
-
node,
|
|
81
|
-
message: `Hook "${name}" has ${String(imperativeCount)} imperative categories and must be extracted to a hooks/ folder. ` +
|
|
82
|
-
"See docs/code-organization-guide/rules/hook-extraction-rule.md",
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
else if (imperativeCount < 2 && inSupportFolder) {
|
|
86
|
-
context.report({
|
|
87
|
-
node,
|
|
88
|
-
message: `Hook "${name}" has fewer than two imperative categories and must stay inline in its consumer file. ` +
|
|
89
|
-
"See docs/code-organization-guide/rules/hook-extraction-rule.md",
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
FunctionDeclaration(node) {
|
|
95
|
-
const exported = node.parent?.type === "ExportNamedDeclaration";
|
|
96
|
-
checkHook(node, node.id?.name, node.body, exported);
|
|
97
|
-
},
|
|
98
|
-
VariableDeclarator(node) {
|
|
99
|
-
if (node.id.type !== "Identifier")
|
|
100
|
-
return;
|
|
101
|
-
const exported = node.parent.parent?.type === "ExportNamedDeclaration";
|
|
102
|
-
if (!exported)
|
|
103
|
-
return;
|
|
104
|
-
const init = node.init;
|
|
105
|
-
if (!init || (init.type !== "ArrowFunctionExpression" && init.type !== "FunctionExpression")) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (init.body.type !== "BlockStatement")
|
|
109
|
-
return;
|
|
110
|
-
checkHook(node, node.id.name, init.body, true);
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
},
|
|
114
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/hook-extraction
|
|
3
|
-
*
|
|
4
|
-
* A custom hook MUST be extracted to its own file when two or more consumers
|
|
5
|
-
* use it. The file must be read across the tree, because how many files import
|
|
6
|
-
* a hook is not visible from one file.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/code-organization-guide/rules/hook-extraction-rule.md
|
|
9
|
-
*/
|
|
10
|
-
import type { Rule } from "eslint";
|
|
11
|
-
export declare const hookExtractionRule: Rule.RuleModule;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/hook-extraction
|
|
3
|
-
*
|
|
4
|
-
* A custom hook MUST be extracted to its own file when two or more consumers
|
|
5
|
-
* use it. The file must be read across the tree, because how many files import
|
|
6
|
-
* a hook is not visible from one file.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/code-organization-guide/rules/hook-extraction-rule.md
|
|
9
|
-
*/
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { getProjectIndex, symbolKey } from "../project/index.js";
|
|
12
|
-
import { folderSegmentsOf, segmentsOf } from "../project/ccf.js";
|
|
13
|
-
export const hookExtractionRule = {
|
|
14
|
-
meta: {
|
|
15
|
-
schema: [],
|
|
16
|
-
type: "problem",
|
|
17
|
-
docs: {
|
|
18
|
-
description: "Require a custom hook with two or more consumers to live in its own hooks/ file.",
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
create(context) {
|
|
22
|
-
const sourceRoot = path.resolve("src");
|
|
23
|
-
const file = path.resolve(context.filename);
|
|
24
|
-
const segments = segmentsOf(file, sourceRoot);
|
|
25
|
-
if (segments.length === 0)
|
|
26
|
-
return {};
|
|
27
|
-
const index = getProjectIndex(sourceRoot);
|
|
28
|
-
if (!index)
|
|
29
|
-
return {};
|
|
30
|
-
const module = index.modules.get(file);
|
|
31
|
-
if (!module)
|
|
32
|
-
return {};
|
|
33
|
-
const folder = folderSegmentsOf(file, sourceRoot);
|
|
34
|
-
const alreadyInHooksFolder = folder.length > 0 && folder[folder.length - 1] === "hooks";
|
|
35
|
-
const reusedHooks = module.exports
|
|
36
|
-
.filter((exp) => exp.kind === "hook" && !alreadyInHooksFolder)
|
|
37
|
-
.map((exp) => ({ exp, consumers: index.symbolConsumers.get(symbolKey(file, exp.name)) }))
|
|
38
|
-
.filter(({ consumers }) => (consumers?.size ?? 0) >= 2);
|
|
39
|
-
if (reusedHooks.length === 0)
|
|
40
|
-
return {};
|
|
41
|
-
return {
|
|
42
|
-
Program(node) {
|
|
43
|
-
for (const { exp, consumers } of reusedHooks) {
|
|
44
|
-
context.report({
|
|
45
|
-
node,
|
|
46
|
-
loc: { line: exp.line, column: 0 },
|
|
47
|
-
message: `Hook "${exp.name}" has ${String(consumers?.size ?? 0)} consumers; ` +
|
|
48
|
-
"extract it to its own file in a hooks/ folder. " +
|
|
49
|
-
"See docs/code-organization-guide/rules/hook-extraction-rule.md",
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
};
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/import-boundaries
|
|
3
|
-
*
|
|
4
|
-
* Enforces the import conventions from the "Exports and Imports Rule":
|
|
5
|
-
* - Whichever of the relative path and the @/* alias has fewer segments, with
|
|
6
|
-
* a tie going to the relative path.
|
|
7
|
-
* - Layer boundary enforcement (app → compositions → features → shared → root).
|
|
8
|
-
*
|
|
9
|
-
* @see docs/code-organization-guide/rules/exports-and-imports-rule.md
|
|
10
|
-
*/
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
const sourceRoot = path.resolve("src");
|
|
13
|
-
const rootSupportFolders = new Set(["config", "constants", "hooks", "locales", "schemas", "types", "utils"]);
|
|
14
|
-
const moduleExtensions = new Set([".cjs", ".cts", ".js", ".jsx", ".mjs", ".mts", ".ts", ".tsx"]);
|
|
15
|
-
const styleExtensions = new Set([".css", ".less", ".sass", ".scss"]);
|
|
16
|
-
function resolveSourceImport(filename, importPath) {
|
|
17
|
-
if (importPath.startsWith("@/")) {
|
|
18
|
-
return path.resolve(sourceRoot, importPath.slice(2));
|
|
19
|
-
}
|
|
20
|
-
if (importPath.startsWith(".")) {
|
|
21
|
-
return path.resolve(path.dirname(filename), importPath);
|
|
22
|
-
}
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
function sourceSegments(absolutePath) {
|
|
26
|
-
const relativePath = path.relative(sourceRoot, absolutePath);
|
|
27
|
-
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
return relativePath.split(path.sep);
|
|
31
|
-
}
|
|
32
|
-
/** The relative form of an import, always prefixed so it reads as a path. */
|
|
33
|
-
function relativeSpecifier(filename, resolvedPath) {
|
|
34
|
-
const relativePath = path.relative(path.dirname(filename), resolvedPath).split(path.sep).join("/");
|
|
35
|
-
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
36
|
-
}
|
|
37
|
-
/** The `@/*` form of an import. */
|
|
38
|
-
function aliasSpecifier(resolvedPath) {
|
|
39
|
-
return `@/${(sourceSegments(resolvedPath) ?? []).join("/")}`;
|
|
40
|
-
}
|
|
41
|
-
/** Segments in a specifier: one per `../` step and one per name, ignoring a leading `./`. */
|
|
42
|
-
function segmentCount(specifier) {
|
|
43
|
-
return specifier
|
|
44
|
-
.replace(/^@\//, "")
|
|
45
|
-
.split("/")
|
|
46
|
-
.filter((segment) => segment !== "." && segment !== "").length;
|
|
47
|
-
}
|
|
48
|
-
function describeSegments(count) {
|
|
49
|
-
return `${String(count)} segment${count === 1 ? "" : "s"}`;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Whether the relative form is the one to use. Shorter wins, and a tie goes to
|
|
53
|
-
* the relative form. Because crossing a layer always costs at least one `../`
|
|
54
|
-
* while the alias spells the same tail, the alias always wins for a
|
|
55
|
-
* cross-layer import without this needing to know what a layer is.
|
|
56
|
-
*/
|
|
57
|
-
function prefersRelative(filename, resolvedPath) {
|
|
58
|
-
return segmentCount(relativeSpecifier(filename, resolvedPath)) <= segmentCount(aliasSpecifier(resolvedPath));
|
|
59
|
-
}
|
|
60
|
-
export const importBoundariesRule = {
|
|
61
|
-
meta: {
|
|
62
|
-
schema: [],
|
|
63
|
-
type: "problem",
|
|
64
|
-
},
|
|
65
|
-
create(context) {
|
|
66
|
-
const filename = context.filename;
|
|
67
|
-
if (!filename) {
|
|
68
|
-
return {};
|
|
69
|
-
}
|
|
70
|
-
function reportImport(source) {
|
|
71
|
-
if (typeof source.value !== "string") {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const importPath = source.value;
|
|
75
|
-
const resolvedPath = resolveSourceImport(filename, importPath);
|
|
76
|
-
if (!resolvedPath) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const importer = sourceSegments(filename);
|
|
80
|
-
const imported = sourceSegments(resolvedPath);
|
|
81
|
-
if (!importer || !imported || importer.length === 0 || imported.length === 0) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const [importerLayer = "", importerFeature] = importer;
|
|
85
|
-
const [importedLayer = "", importedFeature] = imported;
|
|
86
|
-
const extension = path.extname(importPath);
|
|
87
|
-
const isCodeModule = !extension || moduleExtensions.has(extension);
|
|
88
|
-
const isAppLocalStyleImport = importerLayer === "app" && importedLayer === "app" && styleExtensions.has(extension);
|
|
89
|
-
const importedIsRootSupport = rootSupportFolders.has(importedLayer);
|
|
90
|
-
const importedIsSameConfig = importerLayer === "config" && importedLayer === "config" && importer[1] === imported[1];
|
|
91
|
-
const isAllowed = (!isCodeModule && importerLayer === "app" && isAppLocalStyleImport) ||
|
|
92
|
-
(isCodeModule &&
|
|
93
|
-
importerLayer === "app" &&
|
|
94
|
-
(importedLayer === "compositions" ||
|
|
95
|
-
importedLayer === "features" ||
|
|
96
|
-
importedLayer === "shared" ||
|
|
97
|
-
importedIsRootSupport)) ||
|
|
98
|
-
(isCodeModule &&
|
|
99
|
-
importerLayer === "compositions" &&
|
|
100
|
-
(importedLayer === "compositions" ||
|
|
101
|
-
importedLayer === "features" ||
|
|
102
|
-
importedLayer === "shared" ||
|
|
103
|
-
importedIsRootSupport)) ||
|
|
104
|
-
(isCodeModule &&
|
|
105
|
-
importerLayer === "features" &&
|
|
106
|
-
((importedLayer === "features" && importerFeature === importedFeature) ||
|
|
107
|
-
importedLayer === "shared" ||
|
|
108
|
-
importedIsRootSupport)) ||
|
|
109
|
-
(isCodeModule && importerLayer === "shared" && (importedLayer === "shared" || importedIsRootSupport)) ||
|
|
110
|
-
(isCodeModule &&
|
|
111
|
-
rootSupportFolders.has(importerLayer) &&
|
|
112
|
-
importedIsRootSupport &&
|
|
113
|
-
(importerLayer !== "config" || importedLayer !== "config" || importedIsSameConfig));
|
|
114
|
-
if (!isAllowed) {
|
|
115
|
-
context.report({
|
|
116
|
-
node: source,
|
|
117
|
-
message: "This import violates the src layer boundary.",
|
|
118
|
-
});
|
|
119
|
-
// The fix is to move the file, so how the specifier is spelled does not matter yet.
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const relativeForm = relativeSpecifier(filename, resolvedPath);
|
|
123
|
-
const aliasForm = aliasSpecifier(resolvedPath);
|
|
124
|
-
const relativeSegments = segmentCount(relativeForm);
|
|
125
|
-
const aliasSegments = segmentCount(aliasForm);
|
|
126
|
-
function describeChoice(preferred, preferredSegments, other, otherSegments) {
|
|
127
|
-
const tie = preferredSegments === otherSegments ? ", and a tie goes to the relative path" : "";
|
|
128
|
-
return (`Use "${preferred}" (${describeSegments(preferredSegments)}) ` +
|
|
129
|
-
`instead of "${other}" (${describeSegments(otherSegments)})${tie}.`);
|
|
130
|
-
}
|
|
131
|
-
if (prefersRelative(filename, resolvedPath) && importPath.startsWith("@/")) {
|
|
132
|
-
context.report({
|
|
133
|
-
node: source,
|
|
134
|
-
message: describeChoice(relativeForm, relativeSegments, aliasForm, aliasSegments),
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (!prefersRelative(filename, resolvedPath) && importPath.startsWith(".")) {
|
|
139
|
-
context.report({
|
|
140
|
-
node: source,
|
|
141
|
-
message: describeChoice(aliasForm, aliasSegments, relativeForm, relativeSegments),
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
ImportDeclaration(node) {
|
|
147
|
-
reportImport(node.source);
|
|
148
|
-
},
|
|
149
|
-
ExportAllDeclaration(node) {
|
|
150
|
-
reportImport(node.source);
|
|
151
|
-
},
|
|
152
|
-
ExportNamedDeclaration(node) {
|
|
153
|
-
if (node.source) {
|
|
154
|
-
reportImport(node.source);
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
ImportExpression(node) {
|
|
158
|
-
reportImport(node.source);
|
|
159
|
-
},
|
|
160
|
-
CallExpression(node) {
|
|
161
|
-
if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments[0]) {
|
|
162
|
-
reportImport(node.arguments[0]);
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
},
|
|
167
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { resolveSpecifier } from "../project/index.js";
|
|
3
|
-
import { segmentsOf } from "../project/ccf.js";
|
|
4
|
-
export const importThroughIndexRule = {
|
|
5
|
-
meta: {
|
|
6
|
-
schema: [],
|
|
7
|
-
type: "problem",
|
|
8
|
-
docs: {
|
|
9
|
-
description: "Require imports of support files to use their support-folder index.",
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
create(context) {
|
|
13
|
-
const filename = path.resolve(context.filename);
|
|
14
|
-
const sourceRoot = sourceRootOf(filename);
|
|
15
|
-
return {
|
|
16
|
-
Program(node) {
|
|
17
|
-
for (const specifier of importSpecifiers(context.sourceCode.text)) {
|
|
18
|
-
const target = resolveSpecifier(filename, specifier, sourceRoot);
|
|
19
|
-
if (!target)
|
|
20
|
-
continue;
|
|
21
|
-
const targetSegments = segmentsOf(target, sourceRoot);
|
|
22
|
-
const supportFolderIndex = targetSegments.findIndex((segment) => ["constants", "types", "schemas"].includes(segment));
|
|
23
|
-
const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : undefined;
|
|
24
|
-
if (!supportFolder || path.basename(target).startsWith("index."))
|
|
25
|
-
continue;
|
|
26
|
-
const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
|
|
27
|
-
const expected = `@/${folderIndex.join("/")}`;
|
|
28
|
-
context.report({
|
|
29
|
-
node,
|
|
30
|
-
message: `Import support files through ${expected} instead of "${specifier}". ` +
|
|
31
|
-
"See the code-organization-guide exports and imports rules.",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
function importSpecifiers(source) {
|
|
39
|
-
const specifiers = [];
|
|
40
|
-
const pattern = /\bimport\s+(?:(?:type\s+)?[\s\S]*?\sfrom\s+)?["'](?<specifier>[^"']+)["']/g;
|
|
41
|
-
for (const match of source.matchAll(pattern)) {
|
|
42
|
-
const specifier = match.groups?.specifier;
|
|
43
|
-
if (specifier)
|
|
44
|
-
specifiers.push(specifier);
|
|
45
|
-
}
|
|
46
|
-
return specifiers;
|
|
47
|
-
}
|
|
48
|
-
function sourceRootOf(filename) {
|
|
49
|
-
const marker = `${path.sep}src${path.sep}`;
|
|
50
|
-
const srcIndex = filename.lastIndexOf(marker);
|
|
51
|
-
return srcIndex >= 0 ? filename.slice(0, srcIndex + marker.length - 1) : path.resolve("src");
|
|
52
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/interactive-component
|
|
3
|
-
*
|
|
4
|
-
* Interactive elements are component boundaries when they are mixed with other
|
|
5
|
-
* content in the same component. Standalone interactive components are valid.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/code-organization-guide/rules/interactive-component-rule.md
|
|
8
|
-
*/
|
|
9
|
-
import type { Rule } from "eslint";
|
|
10
|
-
export declare const interactiveComponentRule: Rule.RuleModule;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/interactive-component
|
|
3
|
-
*
|
|
4
|
-
* Interactive elements are component boundaries when they are mixed with other
|
|
5
|
-
* content in the same component. Standalone interactive components are valid.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/code-organization-guide/rules/interactive-component-rule.md
|
|
8
|
-
*/
|
|
9
|
-
const INTERACTIVE_TAGS = new Set([
|
|
10
|
-
"a",
|
|
11
|
-
"button",
|
|
12
|
-
"details",
|
|
13
|
-
"embed",
|
|
14
|
-
"iframe",
|
|
15
|
-
"input",
|
|
16
|
-
"label",
|
|
17
|
-
"select",
|
|
18
|
-
"textarea",
|
|
19
|
-
"video",
|
|
20
|
-
]);
|
|
21
|
-
function tagName(node) {
|
|
22
|
-
const name = node.openingElement?.name;
|
|
23
|
-
if (name?.type !== "JSXIdentifier")
|
|
24
|
-
return undefined;
|
|
25
|
-
return name.name;
|
|
26
|
-
}
|
|
27
|
-
function isInteractive(node) {
|
|
28
|
-
const name = tagName(node);
|
|
29
|
-
return name !== undefined && INTERACTIVE_TAGS.has(name);
|
|
30
|
-
}
|
|
31
|
-
function isComponentElement(node) {
|
|
32
|
-
const name = tagName(node);
|
|
33
|
-
return name !== undefined && /^[A-Z]/.test(name);
|
|
34
|
-
}
|
|
35
|
-
function isComponentReturn(node) {
|
|
36
|
-
const parent = node.parent;
|
|
37
|
-
if (parent.type === "ReturnStatement")
|
|
38
|
-
return true;
|
|
39
|
-
return parent.type === "ArrowFunctionExpression" && parent.body === node;
|
|
40
|
-
}
|
|
41
|
-
export const interactiveComponentRule = {
|
|
42
|
-
meta: {
|
|
43
|
-
schema: [],
|
|
44
|
-
type: "problem",
|
|
45
|
-
docs: {
|
|
46
|
-
description: "Require interactive HTML elements mixed with other content to be extracted into components.",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
create(context) {
|
|
50
|
-
if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx"))
|
|
51
|
-
return {};
|
|
52
|
-
return {
|
|
53
|
-
JSXElement(node) {
|
|
54
|
-
if (!isInteractive(node))
|
|
55
|
-
return;
|
|
56
|
-
if (isComponentElement(node.parent)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (isInteractive(node.parent)) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (isComponentReturn(node)) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const name = tagName(node);
|
|
66
|
-
if (name === undefined)
|
|
67
|
-
return;
|
|
68
|
-
context.report({
|
|
69
|
-
node,
|
|
70
|
-
message: `Extract the interactive <${name}> into a descriptive component. ` +
|
|
71
|
-
"See docs/code-organization-guide/rules/interactive-component-rule.md",
|
|
72
|
-
});
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* All JSON rules for the package.json checks.
|
|
3
|
-
*/
|
|
4
|
-
import { noCacheFlagRule } from "./no-cache-flag.js";
|
|
5
|
-
import { noVulykDependencyRule } from "./no-vulyk-dependency.js";
|
|
6
|
-
export const jsonRules = {
|
|
7
|
-
"no-cache-flag": noCacheFlagRule,
|
|
8
|
-
"no-vulyk-dependency": noVulykDependencyRule,
|
|
9
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-cache-flag
|
|
3
|
-
*
|
|
4
|
-
* A lint command MUST NOT pass ESLint's --cache flag, because rules that
|
|
5
|
-
* compare a file against the rest of the tree need every file in the run.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
import type { JSONRuleDefinition } from "@eslint/json";
|
|
10
|
-
export declare const noCacheFlagRule: JSONRuleDefinition;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-cache-flag
|
|
3
|
-
*
|
|
4
|
-
* A lint command MUST NOT pass ESLint's --cache flag, because rules that
|
|
5
|
-
* compare a file against the rest of the tree need every file in the run.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
function memberName(member) {
|
|
10
|
-
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
11
|
-
}
|
|
12
|
-
export const noCacheFlagRule = {
|
|
13
|
-
meta: {
|
|
14
|
-
schema: [],
|
|
15
|
-
type: "problem",
|
|
16
|
-
docs: {
|
|
17
|
-
description: "Require lint scripts to not pass ESLint's --cache flag.",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
create(context) {
|
|
21
|
-
return {
|
|
22
|
-
Document(node) {
|
|
23
|
-
const root = node.body;
|
|
24
|
-
if (root.type !== "Object")
|
|
25
|
-
return;
|
|
26
|
-
const scripts = root.members.find((member) => memberName(member) === "scripts");
|
|
27
|
-
if (scripts?.value.type !== "Object")
|
|
28
|
-
return;
|
|
29
|
-
for (const member of scripts.value.members) {
|
|
30
|
-
const name = memberName(member);
|
|
31
|
-
if (name !== "lint" && name !== "fix")
|
|
32
|
-
continue;
|
|
33
|
-
if (member.value.type !== "String")
|
|
34
|
-
continue;
|
|
35
|
-
if (member.value.value.includes("--cache")) {
|
|
36
|
-
context.report({
|
|
37
|
-
node: member,
|
|
38
|
-
message: "Lint scripts must not pass ESLint's --cache flag because cross-file rules require every file in the run.",
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-vulyk-dependency
|
|
3
|
-
*
|
|
4
|
-
* Vulyk MUST NOT be added to package.json in order to run its CLI; it MUST run
|
|
5
|
-
* as an ephemeral command such as npx vulyk@latest.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
import type { JSONRuleDefinition } from "@eslint/json";
|
|
10
|
-
export declare const noVulykDependencyRule: JSONRuleDefinition;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-vulyk-dependency
|
|
3
|
-
*
|
|
4
|
-
* Vulyk MUST NOT be added to package.json in order to run its CLI; it MUST run
|
|
5
|
-
* as an ephemeral command such as npx vulyk@latest.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
function memberName(member) {
|
|
10
|
-
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
11
|
-
}
|
|
12
|
-
export const noVulykDependencyRule = {
|
|
13
|
-
meta: {
|
|
14
|
-
schema: [],
|
|
15
|
-
type: "problem",
|
|
16
|
-
docs: {
|
|
17
|
-
description: "Require vulyk to run as an ephemeral command, not a package.json dependency.",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
create(context) {
|
|
21
|
-
return {
|
|
22
|
-
Document(node) {
|
|
23
|
-
const root = node.body;
|
|
24
|
-
if (root.type !== "Object")
|
|
25
|
-
return;
|
|
26
|
-
for (const key of ["dependencies", "devDependencies"]) {
|
|
27
|
-
const section = root.members.find((member) => memberName(member) === key);
|
|
28
|
-
if (section?.value.type !== "Object")
|
|
29
|
-
continue;
|
|
30
|
-
const vulyk = section.value.members.find((member) => memberName(member) === "vulyk");
|
|
31
|
-
if (vulyk) {
|
|
32
|
-
context.report({
|
|
33
|
-
node: vulyk,
|
|
34
|
-
message: 'Vulyk must not be a package.json dependency. Run it as "npx vulyk@latest" instead.',
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared RuleTester wiring for the package.json rule tests.
|
|
3
|
-
*
|
|
4
|
-
* Each `describe` title in a rule test is the exact text of the requirement the
|
|
5
|
-
* case pins, which is how `pasika coverage` verifies that a requirement recorded
|
|
6
|
-
* as lint-enforced has a test behind it.
|
|
7
|
-
*/
|
|
8
|
-
import { describe } from "node:test";
|
|
9
|
-
import { RuleTester } from "eslint";
|
|
10
|
-
export declare const jsonRuleTester: RuleTester;
|
|
11
|
-
export { describe };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared RuleTester wiring for the package.json rule tests.
|
|
3
|
-
*
|
|
4
|
-
* Each `describe` title in a rule test is the exact text of the requirement the
|
|
5
|
-
* case pins, which is how `pasika coverage` verifies that a requirement recorded
|
|
6
|
-
* as lint-enforced has a test behind it.
|
|
7
|
-
*/
|
|
8
|
-
import { describe, it } from "node:test";
|
|
9
|
-
import { RuleTester } from "eslint";
|
|
10
|
-
import jsonPlugin from "@eslint/json";
|
|
11
|
-
import { jsonRules } from "../../index.js";
|
|
12
|
-
RuleTester.describe = describe;
|
|
13
|
-
RuleTester.it = it;
|
|
14
|
-
export const jsonRuleTester = new RuleTester({
|
|
15
|
-
language: "json/json",
|
|
16
|
-
plugins: {
|
|
17
|
-
json: { languages: { json: jsonPlugin.languages.json } },
|
|
18
|
-
pasika: { rules: jsonRules },
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
export { describe };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/jsx-hygiene
|
|
3
|
-
*
|
|
4
|
-
* Keeps calculations and complex conditions out of JSX children and attributes.
|
|
5
|
-
*
|
|
6
|
-
* @see docs/code-organization-guide/rules/jsx-hygiene-rule.md
|
|
7
|
-
*/
|
|
8
|
-
import type { Rule } from "eslint";
|
|
9
|
-
export declare const jsxHygieneRule: Rule.RuleModule;
|