pasika 0.3.2 → 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 +72 -20
- 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 +5 -6
- package/dist/cli/output.d.ts +0 -3
- package/dist/cli/output.js +0 -11
- package/dist/enforcement/coverage.d.ts +0 -51
- package/dist/enforcement/coverage.js +0 -206
- 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 -94
- 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
- package/enforcement/registry.json +0 -1145
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Shared project index for cross-document markdown rules.
|
|
3
|
-
* Scans the docs folder and caches document metadata.
|
|
4
|
-
*/
|
|
5
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
const KIND_BY_SUFFIX = [
|
|
8
|
-
["-rule.md", "rule"],
|
|
9
|
-
["-guide.md", "guide"],
|
|
10
|
-
["-reference.md", "reference"],
|
|
11
|
-
["-policy.md", "policy"],
|
|
12
|
-
];
|
|
13
|
-
function listMarkdownFiles(dir) {
|
|
14
|
-
return readdirSync(dir).flatMap((entry) => {
|
|
15
|
-
const entryPath = path.join(dir, entry);
|
|
16
|
-
if (statSync(entryPath).isDirectory()) {
|
|
17
|
-
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
18
|
-
}
|
|
19
|
-
return entry.endsWith(".md") ? [entryPath] : [];
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function extractTitle(filePath) {
|
|
23
|
-
const content = readFileSync(filePath, "utf8");
|
|
24
|
-
const match = /^# (?<title>.+)$/m.exec(content);
|
|
25
|
-
return match?.groups?.title?.trim() ?? "";
|
|
26
|
-
}
|
|
27
|
-
// Memoized cache keyed by docsRoot
|
|
28
|
-
const cache = new Map();
|
|
29
|
-
/**
|
|
30
|
-
* Get all documents in the docs folder. Results are cached per docsRoot.
|
|
31
|
-
*/
|
|
32
|
-
export function getProjectDocs(docsRoot) {
|
|
33
|
-
const cached = cache.get(docsRoot);
|
|
34
|
-
if (cached)
|
|
35
|
-
return cached;
|
|
36
|
-
const files = listMarkdownFiles(docsRoot);
|
|
37
|
-
const docs = files
|
|
38
|
-
.sort((a, b) => a.localeCompare(b))
|
|
39
|
-
.map((filePath) => {
|
|
40
|
-
const fileName = path.basename(filePath);
|
|
41
|
-
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
42
|
-
return {
|
|
43
|
-
filePath,
|
|
44
|
-
doc: path.relative(docsRoot, filePath).split(path.sep).join("/"),
|
|
45
|
-
fileName,
|
|
46
|
-
kind,
|
|
47
|
-
title: extractTitle(filePath),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
cache.set(docsRoot, docs);
|
|
51
|
-
return docs;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Try to find the docsRoot from a file path by walking up to find the docs/ directory.
|
|
55
|
-
*/
|
|
56
|
-
export function findDocsRoot(filePath) {
|
|
57
|
-
let dir = path.dirname(filePath);
|
|
58
|
-
for (;;) {
|
|
59
|
-
if (path.basename(dir) === "docs" && statSync(dir).isDirectory()) {
|
|
60
|
-
return dir;
|
|
61
|
-
}
|
|
62
|
-
const parent = path.dirname(dir);
|
|
63
|
-
if (parent === dir)
|
|
64
|
-
return undefined;
|
|
65
|
-
dir = parent;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Reference block headings rule.
|
|
3
|
-
* A Reference with a single lookup block must not add a section heading for it.
|
|
4
|
-
*/
|
|
5
|
-
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
6
|
-
export declare const referenceBlockHeadingsRule: MarkdownRuleDefinition;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getFilename } from "./helpers.js";
|
|
2
|
-
export const referenceBlockHeadingsRule = {
|
|
3
|
-
meta: {
|
|
4
|
-
type: "problem",
|
|
5
|
-
docs: {
|
|
6
|
-
description: "Reference block headings rule.",
|
|
7
|
-
recommended: true,
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
create(context) {
|
|
11
|
-
return {
|
|
12
|
-
root(node) {
|
|
13
|
-
const filename = getFilename(context);
|
|
14
|
-
if (!filename.endsWith("-reference.md"))
|
|
15
|
-
return;
|
|
16
|
-
const sectionHeadings = [];
|
|
17
|
-
for (const child of node.children) {
|
|
18
|
-
if (child.type === "heading" && child.depth >= 2) {
|
|
19
|
-
sectionHeadings.push(child);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (sectionHeadings.length === 1) {
|
|
23
|
-
context.report({
|
|
24
|
-
node: node.children[0] ?? node,
|
|
25
|
-
message: "reference has exactly one section heading, so either a single block is headed or a first block is not",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
},
|
|
31
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Reference documents must not use RFC 2119 vocabulary in prose.
|
|
3
|
-
* Only naming a keyword in code spans is permitted.
|
|
4
|
-
*/
|
|
5
|
-
import type { MarkdownRuleDefinition } from "@eslint/markdown";
|
|
6
|
-
export declare const referenceNoRfcVocabularyRule: MarkdownRuleDefinition;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { containsRfcKeyword, getFilename } from "./helpers.js";
|
|
2
|
-
export const referenceNoRfcVocabularyRule = {
|
|
3
|
-
meta: {
|
|
4
|
-
type: "problem",
|
|
5
|
-
docs: {
|
|
6
|
-
description: "Reference documents must not use RFC 2119 vocabulary (MUST, MUST NOT, SHOULD, SHOULD NOT, MAY) in prose.",
|
|
7
|
-
recommended: true,
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
create(context) {
|
|
11
|
-
return {
|
|
12
|
-
text(node) {
|
|
13
|
-
const filename = getFilename(context);
|
|
14
|
-
if (!filename.endsWith("-reference.md"))
|
|
15
|
-
return;
|
|
16
|
-
const keyword = containsRfcKeyword(node.value);
|
|
17
|
-
if (keyword) {
|
|
18
|
-
context.report({
|
|
19
|
-
node,
|
|
20
|
-
message: `reference uses RFC 2119 vocabulary: ${keyword}`,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { containsRfcKeyword, getFilename, getTextContent } from "./helpers.js";
|
|
2
|
-
/** Check whether a subtree contains a bullet with RFC vocabulary. */
|
|
3
|
-
function hasRequirement(node) {
|
|
4
|
-
if (node.type === "listItem") {
|
|
5
|
-
if (containsRfcKeyword(getTextContent(node)))
|
|
6
|
-
return true;
|
|
7
|
-
}
|
|
8
|
-
if ("children" in node) {
|
|
9
|
-
for (const child of node.children) {
|
|
10
|
-
if (hasRequirement(child))
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
export const requirementPresentRule = {
|
|
17
|
-
meta: {
|
|
18
|
-
type: "problem",
|
|
19
|
-
docs: {
|
|
20
|
-
description: "Rule and policy documents must state at least one requirement.",
|
|
21
|
-
recommended: true,
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
create(context) {
|
|
25
|
-
return {
|
|
26
|
-
root(node) {
|
|
27
|
-
const filename = getFilename(context);
|
|
28
|
-
if (!filename.endsWith("-rule.md") && !filename.endsWith("-policy.md")) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (!hasRequirement(node)) {
|
|
32
|
-
const kind = filename.endsWith("-rule.md") ? "rule" : "policy";
|
|
33
|
-
context.report({
|
|
34
|
-
node,
|
|
35
|
-
message: `${kind} document states no requirement`,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { containsRfcKeyword, getFilename } from "./helpers.js";
|
|
2
|
-
/** Check whether a subtree contains a text node outside a bullet using RFC vocabulary. */
|
|
3
|
-
function checkOutsideBullets(node, context, insideBullet) {
|
|
4
|
-
if (node.type === "listItem") {
|
|
5
|
-
// Everything under a list item is a bullet; stop descending for the
|
|
6
|
-
// outside-bullet check, but nested lists still need walking.
|
|
7
|
-
for (const child of node.children) {
|
|
8
|
-
checkOutsideBullets(child, context, true);
|
|
9
|
-
}
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (node.type === "text") {
|
|
13
|
-
const keyword = containsRfcKeyword(node.value);
|
|
14
|
-
if (keyword && !insideBullet) {
|
|
15
|
-
context.report({
|
|
16
|
-
node,
|
|
17
|
-
message: `RFC 2119 vocabulary "${keyword}" appears outside a bullet point`,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if ("children" in node) {
|
|
23
|
-
for (const child of node.children) {
|
|
24
|
-
checkOutsideBullets(child, context, insideBullet);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export const rfcOnlyInBulletsRule = {
|
|
29
|
-
meta: {
|
|
30
|
-
type: "problem",
|
|
31
|
-
docs: {
|
|
32
|
-
description: "RFC 2119 vocabulary must appear only in bullet points.",
|
|
33
|
-
recommended: true,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
create(context) {
|
|
37
|
-
return {
|
|
38
|
-
root(node) {
|
|
39
|
-
const filename = getFilename(context);
|
|
40
|
-
if (!filename.endsWith("-rule.md"))
|
|
41
|
-
return;
|
|
42
|
-
checkOutsideBullets(node, context, false);
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { getFilename, getLine, getTextContent } from "./helpers.js";
|
|
2
|
-
export const rulePairedExamplesRule = {
|
|
3
|
-
meta: {
|
|
4
|
-
type: "problem",
|
|
5
|
-
docs: {
|
|
6
|
-
description: "Rule must have paired Incorrect/Correct examples.",
|
|
7
|
-
recommended: true,
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
create(context) {
|
|
11
|
-
return {
|
|
12
|
-
root(node) {
|
|
13
|
-
const filename = getFilename(context);
|
|
14
|
-
if (!filename.endsWith("-rule.md"))
|
|
15
|
-
return;
|
|
16
|
-
const exampleHeadings = [];
|
|
17
|
-
for (const child of node.children) {
|
|
18
|
-
if (child.type === "heading" && child.depth === 2) {
|
|
19
|
-
const text = getTextContent(child).trim();
|
|
20
|
-
if (/^(?:Incorrect|Correct)\b/.test(text)) {
|
|
21
|
-
exampleHeadings.push({ line: getLine(child), text });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const incorrect = exampleHeadings.filter((h) => h.text.startsWith("Incorrect"));
|
|
26
|
-
const correct = exampleHeadings.filter((h) => h.text.startsWith("Correct"));
|
|
27
|
-
if (incorrect.length === 0 || incorrect.length !== correct.length) {
|
|
28
|
-
context.report({
|
|
29
|
-
node,
|
|
30
|
-
message: `${String(incorrect.length)} Incorrect and ${String(correct.length)} Correct examples`,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared RuleTester wiring for the markdown 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 mdRuleTester: RuleTester;
|
|
11
|
-
export { describe };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared RuleTester wiring for the markdown 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 markdown from "@eslint/markdown";
|
|
11
|
-
import { mdRules } from "../../index.js";
|
|
12
|
-
RuleTester.describe = describe;
|
|
13
|
-
RuleTester.it = it;
|
|
14
|
-
export const mdRuleTester = new RuleTester({
|
|
15
|
-
language: "markdown/gfm",
|
|
16
|
-
plugins: {
|
|
17
|
-
markdown,
|
|
18
|
-
pasika: { rules: mdRules },
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
export { describe };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Rules must live in rules/, references must live in references/.
|
|
3
|
-
*/
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { getFilename } from "./helpers.js";
|
|
6
|
-
export const supportDocumentPlacementRule = {
|
|
7
|
-
meta: {
|
|
8
|
-
type: "problem",
|
|
9
|
-
docs: {
|
|
10
|
-
description: "Rules must live in rules/, references must live in references/.",
|
|
11
|
-
recommended: true,
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
create(context) {
|
|
15
|
-
return {
|
|
16
|
-
root(node) {
|
|
17
|
-
const filename = getFilename(context);
|
|
18
|
-
if (!filename.endsWith(".md"))
|
|
19
|
-
return;
|
|
20
|
-
const parentFolder = path.basename(path.dirname(filename));
|
|
21
|
-
if (filename.endsWith("-rule.md") && parentFolder !== "rules") {
|
|
22
|
-
context.report({
|
|
23
|
-
node,
|
|
24
|
-
message: `rule lives in "${parentFolder}/" instead of "rules/"`,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (filename.endsWith("-reference.md") && parentFolder !== "references") {
|
|
28
|
-
context.report({
|
|
29
|
-
node,
|
|
30
|
-
message: `reference lives in "${parentFolder}/" instead of "references/"`,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Document title must match the file name in kebab-case.
|
|
3
|
-
*/
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { getFilename, getTextContent } from "./helpers.js";
|
|
6
|
-
function toExpectedFileName(title) {
|
|
7
|
-
return `${title
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
10
|
-
.replace(/^-|-$/g, "")}.md`;
|
|
11
|
-
}
|
|
12
|
-
export const titleMatchesFileNameRule = {
|
|
13
|
-
meta: {
|
|
14
|
-
type: "problem",
|
|
15
|
-
docs: {
|
|
16
|
-
description: "Document title must match the file name in kebab-case.",
|
|
17
|
-
recommended: true,
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
create(context) {
|
|
21
|
-
return {
|
|
22
|
-
heading(node) {
|
|
23
|
-
if (node.depth !== 1)
|
|
24
|
-
return;
|
|
25
|
-
const filename = getFilename(context);
|
|
26
|
-
if (!filename.endsWith(".md"))
|
|
27
|
-
return;
|
|
28
|
-
const title = getTextContent(node).trim();
|
|
29
|
-
const expectedFileName = toExpectedFileName(title);
|
|
30
|
-
const actualFileName = path.basename(filename);
|
|
31
|
-
if (!title) {
|
|
32
|
-
context.report({
|
|
33
|
-
node,
|
|
34
|
-
message: "document has no `# Title` heading",
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
else if (expectedFileName !== actualFileName) {
|
|
38
|
-
context.report({
|
|
39
|
-
node,
|
|
40
|
-
message: `title "${title}" expects file name ${expectedFileName}`,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/named-exports
|
|
3
|
-
*
|
|
4
|
-
* Enforces named exports for application files while allowing framework routing
|
|
5
|
-
* files such as Next.js page.tsx and layout.tsx to use their required default
|
|
6
|
-
* export contract.
|
|
7
|
-
*
|
|
8
|
-
* @see docs/code-organization-guide/rules/exports-and-imports-rule.md
|
|
9
|
-
*/
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
const FRAMEWORK_DEFAULT_EXPORT_FILES = new Set([
|
|
12
|
-
"default",
|
|
13
|
-
"error",
|
|
14
|
-
"layout",
|
|
15
|
-
"loading",
|
|
16
|
-
"not-found",
|
|
17
|
-
"page",
|
|
18
|
-
"route",
|
|
19
|
-
"template",
|
|
20
|
-
]);
|
|
21
|
-
function isFrameworkDefaultExportFile(filename) {
|
|
22
|
-
const normalized = filename.replaceAll(path.sep, "/");
|
|
23
|
-
if (!normalized.includes("/src/app/"))
|
|
24
|
-
return false;
|
|
25
|
-
const basename = path.basename(filename, path.extname(filename));
|
|
26
|
-
return FRAMEWORK_DEFAULT_EXPORT_FILES.has(basename);
|
|
27
|
-
}
|
|
28
|
-
export const namedExportsRule = {
|
|
29
|
-
meta: {
|
|
30
|
-
schema: [],
|
|
31
|
-
type: "problem",
|
|
32
|
-
docs: {
|
|
33
|
-
description: "Require named exports except where a framework requires a default export.",
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
create(context) {
|
|
37
|
-
if (isFrameworkDefaultExportFile(context.filename))
|
|
38
|
-
return {};
|
|
39
|
-
return {
|
|
40
|
-
ExportDefaultDeclaration(node) {
|
|
41
|
-
context.report({
|
|
42
|
-
node,
|
|
43
|
-
message: "Files that export values must use named exports unless a framework or third-party package requires a different export style for that file. " +
|
|
44
|
-
"See docs/code-organization-guide/rules/exports-and-imports-rule.md",
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
},
|
|
49
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-arbitrary-tailwind
|
|
3
|
-
*
|
|
4
|
-
* Enforces the "Arbitrary Value Rule" — no arbitrary-value Tailwind classes.
|
|
5
|
-
*
|
|
6
|
-
* @see docs/styling-guide/rules/arbitrary-value-rule.md
|
|
7
|
-
*/
|
|
8
|
-
import type { Rule } from "eslint";
|
|
9
|
-
export declare const noArbitraryTailwindRule: Rule.RuleModule;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-arbitrary-tailwind
|
|
3
|
-
*
|
|
4
|
-
* Enforces the "Arbitrary Value Rule" — no arbitrary-value Tailwind classes.
|
|
5
|
-
*
|
|
6
|
-
* @see docs/styling-guide/rules/arbitrary-value-rule.md
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Matches Tailwind arbitrary-value utility classes like rounded-[13px], text-[#fff].
|
|
10
|
-
*
|
|
11
|
-
* Does NOT match variant prefixes like min-[400px]:flex-row or md:flex.
|
|
12
|
-
* The negative lookahead (?![-\w]*:) excludes anything followed by a colon + more.
|
|
13
|
-
*
|
|
14
|
-
* Note: Tailwind breakpoint variants (sm:, md:, lg:, etc.) don't use brackets,
|
|
15
|
-
* so they're never matched. Only bracket-based variants like min-[400px]: need
|
|
16
|
-
* the lookahead exclusion.
|
|
17
|
-
*/
|
|
18
|
-
const ARBITRARY_VALUE_RE = /(?:^|(?<=\s))(?:[a-z]+(?:-[a-z]+)*)-\[[^\]]+\](?![-\w]*:)/g;
|
|
19
|
-
const CLASS_HELPERS = new Set(["cn", "clsx", "twMerge", "twJoin"]);
|
|
20
|
-
export const noArbitraryTailwindRule = {
|
|
21
|
-
meta: {
|
|
22
|
-
schema: [],
|
|
23
|
-
type: "problem",
|
|
24
|
-
docs: {
|
|
25
|
-
description: "Disallow Tailwind arbitrary-value classes like rounded-[13px].",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
create(context) {
|
|
29
|
-
function reportClassString(node, value) {
|
|
30
|
-
ARBITRARY_VALUE_RE.lastIndex = 0;
|
|
31
|
-
const match = ARBITRARY_VALUE_RE.exec(value);
|
|
32
|
-
if (!match)
|
|
33
|
-
return;
|
|
34
|
-
context.report({
|
|
35
|
-
node,
|
|
36
|
-
message: `Tailwind arbitrary-value class "${match[0]}" is not allowed. ` +
|
|
37
|
-
"Use a named token or custom utility. " +
|
|
38
|
-
"See docs/styling-guide/rules/arbitrary-value-rule.md",
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
/** Walks every expression a class name can hide in: conditionals, arrays, and object keys. */
|
|
42
|
-
function checkExpression(node, expression) {
|
|
43
|
-
if (!expression)
|
|
44
|
-
return;
|
|
45
|
-
if (expression.type === "Literal") {
|
|
46
|
-
if (typeof expression.value === "string")
|
|
47
|
-
reportClassString(node, expression.value);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (expression.type === "TemplateLiteral") {
|
|
51
|
-
for (const quasi of expression.quasis)
|
|
52
|
-
reportClassString(node, quasi.value.raw);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (expression.type === "LogicalExpression") {
|
|
56
|
-
checkExpression(node, expression.left);
|
|
57
|
-
checkExpression(node, expression.right);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (expression.type === "ConditionalExpression") {
|
|
61
|
-
checkExpression(node, expression.consequent);
|
|
62
|
-
checkExpression(node, expression.alternate);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (expression.type === "ArrayExpression") {
|
|
66
|
-
for (const element of expression.elements)
|
|
67
|
-
checkExpression(node, element);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (expression.type === "ObjectExpression") {
|
|
71
|
-
// clsx and cn accept `{ "px-[3px]": isActive }`, so the keys carry classes.
|
|
72
|
-
for (const property of expression.properties) {
|
|
73
|
-
if (property.type === "Property")
|
|
74
|
-
checkExpression(node, property.key);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
JSXAttribute(node) {
|
|
80
|
-
const attributeName = node.name?.name;
|
|
81
|
-
if (attributeName !== "className" && attributeName !== "class")
|
|
82
|
-
return;
|
|
83
|
-
const value = node.value;
|
|
84
|
-
if (!value)
|
|
85
|
-
return;
|
|
86
|
-
checkExpression(node, value.type === "JSXExpressionContainer" ? value.expression : value);
|
|
87
|
-
},
|
|
88
|
-
CallExpression(node) {
|
|
89
|
-
if (node.callee.type !== "Identifier" || !CLASS_HELPERS.has(node.callee.name))
|
|
90
|
-
return;
|
|
91
|
-
for (const argument of node.arguments) {
|
|
92
|
-
checkExpression(node, argument);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-eslint-disable
|
|
3
|
-
*
|
|
4
|
-
* Code MUST NOT use eslint-disable directives, and a reported violation MUST
|
|
5
|
-
* be fixed instead.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
import type { Rule } from "eslint";
|
|
10
|
-
export declare const noEslintDisableRule: Rule.RuleModule;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-eslint-disable
|
|
3
|
-
*
|
|
4
|
-
* Code MUST NOT use eslint-disable directives, and a reported violation MUST
|
|
5
|
-
* be fixed instead.
|
|
6
|
-
*
|
|
7
|
-
* @see docs/agent-policy.md
|
|
8
|
-
*/
|
|
9
|
-
export const noEslintDisableRule = {
|
|
10
|
-
meta: {
|
|
11
|
-
schema: [],
|
|
12
|
-
type: "problem",
|
|
13
|
-
docs: {
|
|
14
|
-
description: "Forbid eslint-disable directives.",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
create(context) {
|
|
18
|
-
return {
|
|
19
|
-
Program() {
|
|
20
|
-
for (const comment of context.sourceCode.getAllComments()) {
|
|
21
|
-
if (!comment.value.includes("eslint-disable"))
|
|
22
|
-
continue;
|
|
23
|
-
if (!comment.loc)
|
|
24
|
-
continue;
|
|
25
|
-
context.report({
|
|
26
|
-
loc: comment.loc,
|
|
27
|
-
message: "Do not use eslint-disable directives; fix the reported violation instead.",
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
},
|
|
33
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESLint rule: pasika/no-mixed-concerns
|
|
3
|
-
*
|
|
4
|
-
* Enforces the "No Mixed Concerns Rule" — one React component per .tsx file.
|
|
5
|
-
*
|
|
6
|
-
* @see docs/code-organization-guide/rules/no-mixed-concerns-rule.md
|
|
7
|
-
*/
|
|
8
|
-
import type { Rule } from "eslint";
|
|
9
|
-
export declare const noMixedConcernsRule: Rule.RuleModule;
|