pasika 0.3.4 → 0.3.6
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 +3 -0
- package/dist/cli/index.js +517 -159
- package/dist/eslint/pasika/index.d.ts +4 -2
- package/dist/eslint/pasika/index.js +471 -111
- package/package.json +8 -1
|
@@ -586,8 +586,8 @@ var enforceCvaVariantPropsRule = {
|
|
|
586
586
|
if (node.typeAnnotation?.type !== "TSTypeLiteral") return;
|
|
587
587
|
for (const member of node.typeAnnotation.members ?? []) {
|
|
588
588
|
if (member.type !== "TSPropertySignature") continue;
|
|
589
|
-
const
|
|
590
|
-
if (!
|
|
589
|
+
const keyName2 = member.key?.type === "Identifier" ? member.key.name : void 0;
|
|
590
|
+
if (!keyName2) continue;
|
|
591
591
|
const typeAnn = member.typeAnnotation?.typeAnnotation;
|
|
592
592
|
if (typeAnn?.type !== "TSUnionType") continue;
|
|
593
593
|
const allStringLiterals = (typeAnn.types ?? []).every(
|
|
@@ -595,10 +595,10 @@ var enforceCvaVariantPropsRule = {
|
|
|
595
595
|
);
|
|
596
596
|
if (allStringLiterals && (typeAnn.types?.length ?? 0) >= 2) {
|
|
597
597
|
for (const variantNames of cvaDefinitions.values()) {
|
|
598
|
-
if (variantNames.includes(
|
|
598
|
+
if (variantNames.includes(keyName2)) {
|
|
599
599
|
context.report({
|
|
600
600
|
node,
|
|
601
|
-
message: `Variant prop "${
|
|
601
|
+
message: `Variant prop "${keyName2}" duplicates CVA variant values. Use VariantProps<typeof variants> instead of manually writing union types. See docs/styling-guide/rules/component-variant-rule.md`
|
|
602
602
|
});
|
|
603
603
|
return;
|
|
604
604
|
}
|
|
@@ -1334,21 +1334,27 @@ var dataTestIdCaseRule = {
|
|
|
1334
1334
|
Program(node) {
|
|
1335
1335
|
for (const component of components) {
|
|
1336
1336
|
const root = findSimpleRoot(component, text, filename);
|
|
1337
|
-
if (
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1337
|
+
if (root) {
|
|
1338
|
+
const { value } = getTestId(root);
|
|
1339
|
+
const expected = component.smart ? component.name : toKebabCase(component.name);
|
|
1340
|
+
if (component.smart && value === void 0) {
|
|
1341
|
+
context.report({
|
|
1342
|
+
node,
|
|
1343
|
+
message: `Smart component "${component.name}" with one outer DOM element must set data-testid="${expected}".`
|
|
1344
|
+
});
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
if (value === void 0 || value === expected) continue;
|
|
1348
|
+
if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
|
|
1349
|
+
context.report({
|
|
1350
|
+
node,
|
|
1351
|
+
message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
} else if (component.smart) {
|
|
1349
1355
|
context.report({
|
|
1350
1356
|
node,
|
|
1351
|
-
message: `
|
|
1357
|
+
message: `Smart component "${component.name}" has no single outer element; wrap its content in one outer element with data-testid="${component.name}" instead of rendering multiple roots. See docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md`
|
|
1352
1358
|
});
|
|
1353
1359
|
}
|
|
1354
1360
|
}
|
|
@@ -1831,14 +1837,14 @@ var cvaAppearancePropsRule = {
|
|
|
1831
1837
|
if (node.typeAnnotation?.type !== "TSTypeLiteral") return;
|
|
1832
1838
|
for (const member of node.typeAnnotation.members ?? []) {
|
|
1833
1839
|
if (member.type !== "TSPropertySignature") continue;
|
|
1834
|
-
const
|
|
1835
|
-
if (!
|
|
1840
|
+
const keyName2 = member.key?.type === "Identifier" ? member.key.name : void 0;
|
|
1841
|
+
if (!keyName2 || !VISUAL_OPTION_NAMES.has(keyName2)) continue;
|
|
1836
1842
|
const typeAnnotation = member.typeAnnotation?.typeAnnotation ?? member.typeAnnotation;
|
|
1837
1843
|
if (!typeAnnotation) continue;
|
|
1838
1844
|
if (typeAnnotation.type === "TSUnionType" || typeAnnotation.type === "TSTypeLiteral") {
|
|
1839
1845
|
context.report({
|
|
1840
1846
|
node,
|
|
1841
|
-
message: `Visual option prop "${
|
|
1847
|
+
message: `Visual option prop "${keyName2}" must be defined through a cva() call rather than a manual union type. See docs/styling-guide/rules/component-variant-rule.md`
|
|
1842
1848
|
});
|
|
1843
1849
|
}
|
|
1844
1850
|
}
|
|
@@ -1858,15 +1864,15 @@ function extractCvaInfo(node) {
|
|
|
1858
1864
|
if (options?.type !== "ObjectExpression") return { variantNames, compoundVariants };
|
|
1859
1865
|
for (const prop of options.properties) {
|
|
1860
1866
|
if (prop.type !== "Property") continue;
|
|
1861
|
-
const
|
|
1862
|
-
if (
|
|
1867
|
+
const keyName2 = prop.key.type === "Identifier" ? prop.key.name : void 0;
|
|
1868
|
+
if (keyName2 === "variants" && prop.value.type === "ObjectExpression") {
|
|
1863
1869
|
for (const vProp of prop.value.properties) {
|
|
1864
1870
|
if (vProp.type !== "Property") continue;
|
|
1865
1871
|
const vName = vProp.key.type === "Identifier" ? vProp.key.name : void 0;
|
|
1866
1872
|
if (vName) variantNames.add(vName);
|
|
1867
1873
|
}
|
|
1868
1874
|
}
|
|
1869
|
-
if (
|
|
1875
|
+
if (keyName2 === "compoundVariants" && prop.value.type === "ArrayExpression") {
|
|
1870
1876
|
for (const entry of prop.value.elements) {
|
|
1871
1877
|
if (entry?.type !== "ObjectExpression") continue;
|
|
1872
1878
|
for (const cvProp of entry.properties) {
|
|
@@ -2637,8 +2643,279 @@ var localePlacementRule = {
|
|
|
2637
2643
|
}
|
|
2638
2644
|
};
|
|
2639
2645
|
|
|
2640
|
-
// eslint/pasika/rules/
|
|
2646
|
+
// eslint/pasika/rules/sole-state-owner.ts
|
|
2641
2647
|
import path29 from "path";
|
|
2648
|
+
import ts6 from "typescript";
|
|
2649
|
+
function findStateHooks(node) {
|
|
2650
|
+
const hooks = [];
|
|
2651
|
+
const visit = (child) => {
|
|
2652
|
+
if (ts6.isCallExpression(child) && ts6.isIdentifier(child.expression) && child.expression.text === "useState") {
|
|
2653
|
+
if (ts6.isVariableDeclaration(child.parent)) {
|
|
2654
|
+
const { name } = child.parent;
|
|
2655
|
+
if (ts6.isArrayBindingPattern(name) && name.elements.length >= 2) {
|
|
2656
|
+
const value = name.elements[0];
|
|
2657
|
+
const updater = name.elements[1];
|
|
2658
|
+
if (value && updater && ts6.isBindingElement(value) && ts6.isBindingElement(updater)) {
|
|
2659
|
+
const valueName = value.name;
|
|
2660
|
+
const updaterName = updater.name;
|
|
2661
|
+
if (ts6.isIdentifier(valueName) && ts6.isIdentifier(updaterName)) {
|
|
2662
|
+
hooks.push({ value: valueName.text, updater: updaterName.text });
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
ts6.forEachChild(child, visit);
|
|
2669
|
+
};
|
|
2670
|
+
visit(node);
|
|
2671
|
+
return hooks;
|
|
2672
|
+
}
|
|
2673
|
+
function isHookUsage(node, hook) {
|
|
2674
|
+
if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && node.expression.text === hook.updater) {
|
|
2675
|
+
return "updater";
|
|
2676
|
+
}
|
|
2677
|
+
if (ts6.isIdentifier(node) && node.text === hook.value) {
|
|
2678
|
+
const parent = node.parent;
|
|
2679
|
+
if (ts6.isBindingElement(parent) || ts6.isPropertyAccessExpression(parent) || ts6.isShorthandPropertyAssignment(parent)) {
|
|
2680
|
+
return void 0;
|
|
2681
|
+
}
|
|
2682
|
+
return "value";
|
|
2683
|
+
}
|
|
2684
|
+
return void 0;
|
|
2685
|
+
}
|
|
2686
|
+
function topLevelJsxChildren(initial) {
|
|
2687
|
+
if (!initial) return void 0;
|
|
2688
|
+
let expression = initial;
|
|
2689
|
+
while (ts6.isParenthesizedExpression(expression)) expression = expression.expression;
|
|
2690
|
+
if (ts6.isJsxFragment(expression)) {
|
|
2691
|
+
const children = expression.children.filter(
|
|
2692
|
+
(c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
|
|
2693
|
+
);
|
|
2694
|
+
return { root: expression, children };
|
|
2695
|
+
}
|
|
2696
|
+
if (ts6.isJsxElement(expression)) {
|
|
2697
|
+
const children = expression.children.filter(
|
|
2698
|
+
(c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
|
|
2699
|
+
);
|
|
2700
|
+
return { root: expression, children };
|
|
2701
|
+
}
|
|
2702
|
+
return void 0;
|
|
2703
|
+
}
|
|
2704
|
+
function collectUsesIn(child, hook) {
|
|
2705
|
+
const positions = [];
|
|
2706
|
+
let count = 0;
|
|
2707
|
+
const visit = (node) => {
|
|
2708
|
+
if (isHookUsage(node, hook)) {
|
|
2709
|
+
positions.push(node);
|
|
2710
|
+
count += 1;
|
|
2711
|
+
}
|
|
2712
|
+
if (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node)) return;
|
|
2713
|
+
ts6.forEachChild(node, visit);
|
|
2714
|
+
};
|
|
2715
|
+
visit(child);
|
|
2716
|
+
return { positions, count };
|
|
2717
|
+
}
|
|
2718
|
+
var soleStateOwnerRule = {
|
|
2719
|
+
meta: {
|
|
2720
|
+
schema: [],
|
|
2721
|
+
type: "problem",
|
|
2722
|
+
docs: {
|
|
2723
|
+
description: "Require extracting a JSX part that is the sole owner of a useState hook as a named component."
|
|
2724
|
+
}
|
|
2725
|
+
},
|
|
2726
|
+
create(context) {
|
|
2727
|
+
const filename = path29.resolve(context.filename);
|
|
2728
|
+
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
|
|
2729
|
+
const text = context.sourceCode.text;
|
|
2730
|
+
const components = parseComponentInfo(text, filename);
|
|
2731
|
+
return {
|
|
2732
|
+
Program(node) {
|
|
2733
|
+
for (const component of components) {
|
|
2734
|
+
const hooks = findStateHooks(component.declaration);
|
|
2735
|
+
for (const hook of hooks) {
|
|
2736
|
+
const result = analyzeSoleOwner(component.declaration, hook);
|
|
2737
|
+
if (!result) continue;
|
|
2738
|
+
const { run, totalTopLevel } = result;
|
|
2739
|
+
if (run === 0 || totalTopLevel === 0) continue;
|
|
2740
|
+
context.report({
|
|
2741
|
+
node,
|
|
2742
|
+
loc: { line: 1, column: 0 },
|
|
2743
|
+
message: `Component "${component.name}" uses state "${hook.value}" in ${String(run)} contiguous top-level JSX part${run === 1 ? "" : "s"}; extract that part into a named component that owns useState instead of reading it in the parent. See docs/code-organization-guide/rules/sole-state-owner-rule.md`
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
function analyzeSoleOwner(declaration, hook) {
|
|
2752
|
+
const body = ts6.isFunctionDeclaration(declaration) ? declaration.body : void 0;
|
|
2753
|
+
if (!body || !ts6.isBlock(body)) return void 0;
|
|
2754
|
+
const returns = [];
|
|
2755
|
+
const visit = (node) => {
|
|
2756
|
+
if (node !== body && (ts6.isFunctionLike(node) || ts6.isClassLike(node))) return;
|
|
2757
|
+
if (ts6.isReturnStatement(node)) returns.push(node);
|
|
2758
|
+
ts6.forEachChild(node, visit);
|
|
2759
|
+
};
|
|
2760
|
+
visit(body);
|
|
2761
|
+
if (returns.length !== 1) return void 0;
|
|
2762
|
+
const single = returns[0];
|
|
2763
|
+
if (!single?.expression || !ts6.isExpression(single.expression)) return void 0;
|
|
2764
|
+
const returnExpression = single.expression;
|
|
2765
|
+
const root = topLevelJsxChildren(returnExpression);
|
|
2766
|
+
if (!root || root.children.length === 0) return void 0;
|
|
2767
|
+
const totalTopLevel = root.children.length;
|
|
2768
|
+
const usagePerChild = root.children.map((child) => collectUsesIn(child, hook));
|
|
2769
|
+
const outsideJsx = usesOutsideJsx(declaration, hook, root.children);
|
|
2770
|
+
if (outsideJsx) return void 0;
|
|
2771
|
+
const usedLabels = usagePerChild.map((u) => u.count > 0);
|
|
2772
|
+
const firstUsed = usedLabels.findIndex((v) => v);
|
|
2773
|
+
const lastUsed = usedLabels.lastIndexOf(true);
|
|
2774
|
+
if (firstUsed === -1 || lastUsed === -1) return void 0;
|
|
2775
|
+
const windowHasUnused = usedLabels.slice(firstUsed, lastUsed + 1).some((v) => !v);
|
|
2776
|
+
if (windowHasUnused) return void 0;
|
|
2777
|
+
const run = lastUsed - firstUsed + 1;
|
|
2778
|
+
const hasOutside = firstUsed > 0 || lastUsed < totalTopLevel - 1;
|
|
2779
|
+
if (!hasOutside) return void 0;
|
|
2780
|
+
return { run, totalTopLevel };
|
|
2781
|
+
}
|
|
2782
|
+
function usesOutsideJsx(declaration, hook, children) {
|
|
2783
|
+
let outside = false;
|
|
2784
|
+
const visit = (node) => {
|
|
2785
|
+
if (outside) return;
|
|
2786
|
+
if (node !== declaration && (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node))) return;
|
|
2787
|
+
if (isHookUsage(node, hook)) {
|
|
2788
|
+
let current = node;
|
|
2789
|
+
let isInJsxChild = false;
|
|
2790
|
+
while (!ts6.isSourceFile(current)) {
|
|
2791
|
+
if (children.includes(current)) {
|
|
2792
|
+
isInJsxChild = true;
|
|
2793
|
+
break;
|
|
2794
|
+
}
|
|
2795
|
+
current = current.parent;
|
|
2796
|
+
}
|
|
2797
|
+
if (!isInJsxChild) outside = true;
|
|
2798
|
+
}
|
|
2799
|
+
ts6.forEachChild(node, visit);
|
|
2800
|
+
};
|
|
2801
|
+
visit(declaration);
|
|
2802
|
+
return outside;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
// eslint/pasika/rules/locale-key-shape.ts
|
|
2806
|
+
import path30 from "path";
|
|
2807
|
+
var MAX_KEY_LENGTH = 30;
|
|
2808
|
+
var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
|
|
2809
|
+
"Button",
|
|
2810
|
+
"Link",
|
|
2811
|
+
"Dialog",
|
|
2812
|
+
"AlertDialog",
|
|
2813
|
+
"Tooltip",
|
|
2814
|
+
"Banner",
|
|
2815
|
+
"Alert",
|
|
2816
|
+
"Status",
|
|
2817
|
+
"Tab",
|
|
2818
|
+
"TabPanel",
|
|
2819
|
+
"Menu",
|
|
2820
|
+
"MenuItem",
|
|
2821
|
+
"MenuBar",
|
|
2822
|
+
"Checkbox",
|
|
2823
|
+
"Radio",
|
|
2824
|
+
"RadioGroup",
|
|
2825
|
+
"Switch",
|
|
2826
|
+
"ComboBox",
|
|
2827
|
+
"ListBox",
|
|
2828
|
+
"Option",
|
|
2829
|
+
"Search",
|
|
2830
|
+
"SearchBox",
|
|
2831
|
+
"Navigation",
|
|
2832
|
+
"ProgressBar",
|
|
2833
|
+
"Slider",
|
|
2834
|
+
"SpinButton",
|
|
2835
|
+
"TextBox",
|
|
2836
|
+
"Tree",
|
|
2837
|
+
"TreeItem",
|
|
2838
|
+
"TreeGrid",
|
|
2839
|
+
"Grid",
|
|
2840
|
+
"GridCell",
|
|
2841
|
+
"Feed",
|
|
2842
|
+
"Log",
|
|
2843
|
+
"Timer",
|
|
2844
|
+
"Marquee",
|
|
2845
|
+
"Region",
|
|
2846
|
+
"Main",
|
|
2847
|
+
"Complementary",
|
|
2848
|
+
"ContentInfo",
|
|
2849
|
+
"Form",
|
|
2850
|
+
"Toolbar",
|
|
2851
|
+
"ScrollBar"
|
|
2852
|
+
]);
|
|
2853
|
+
var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
|
|
2854
|
+
function isLocalesFile2(filename) {
|
|
2855
|
+
const segments = path30.resolve(filename).split(path30.sep);
|
|
2856
|
+
const srcIdx = segments.lastIndexOf("src");
|
|
2857
|
+
return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
|
|
2858
|
+
}
|
|
2859
|
+
function keyName(key) {
|
|
2860
|
+
if (key.type === "Identifier") return key.name;
|
|
2861
|
+
if (key.type === "Literal" && typeof key.value === "string") return key.value;
|
|
2862
|
+
return void 0;
|
|
2863
|
+
}
|
|
2864
|
+
function reportFor(context, node, message) {
|
|
2865
|
+
context.report({ node, message });
|
|
2866
|
+
}
|
|
2867
|
+
function checkKey(context, node, name) {
|
|
2868
|
+
if (!CAMEL_CASE.test(name)) {
|
|
2869
|
+
reportFor(
|
|
2870
|
+
context,
|
|
2871
|
+
node,
|
|
2872
|
+
`Locale key "${name}" must be camelCase English based on the text. See docs/code-organization-guide/rules/locales-rule.md`
|
|
2873
|
+
);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
if (name.length <= MAX_KEY_LENGTH) return;
|
|
2877
|
+
const hasRolePostfix = [...ROLE_POSTFIXES].some((role) => name.endsWith(role));
|
|
2878
|
+
if (hasRolePostfix) return;
|
|
2879
|
+
reportFor(
|
|
2880
|
+
context,
|
|
2881
|
+
node,
|
|
2882
|
+
`Locale key "${name}" is longer than ${String(MAX_KEY_LENGTH)} characters, so it must describe the message's purpose instead of the text; end it with an element role such as "Button", "Link", or "Dialog" (see WAI-ARIA roles). See docs/code-organization-guide/rules/locales-rule.md`
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
var localeKeyShapeRule = {
|
|
2886
|
+
meta: {
|
|
2887
|
+
schema: [],
|
|
2888
|
+
type: "problem",
|
|
2889
|
+
docs: {
|
|
2890
|
+
description: "Require locale keys to be camelCase, and purpose-based element roles when long."
|
|
2891
|
+
}
|
|
2892
|
+
},
|
|
2893
|
+
create(context) {
|
|
2894
|
+
if (!isLocalesFile2(context.filename)) return {};
|
|
2895
|
+
function visitObject(node) {
|
|
2896
|
+
for (const property of node.properties) {
|
|
2897
|
+
if (property.type !== "Property") continue;
|
|
2898
|
+
const name = keyName(property.key);
|
|
2899
|
+
if (name === void 0) continue;
|
|
2900
|
+
if (property.value.type === "ObjectExpression") {
|
|
2901
|
+
visitObject(property.value);
|
|
2902
|
+
} else if (property.value.type === "Literal" && typeof property.value.value === "string") {
|
|
2903
|
+
checkKey(context, property, name);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
return {
|
|
2908
|
+
VariableDeclarator(node) {
|
|
2909
|
+
if (node.id.type !== "Identifier" || node.id.name !== "locales") return;
|
|
2910
|
+
if (node.init?.type !== "ObjectExpression") return;
|
|
2911
|
+
visitObject(node.init);
|
|
2912
|
+
}
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2917
|
+
// eslint/pasika/rules/shared-style-dedup.ts
|
|
2918
|
+
import path31 from "path";
|
|
2642
2919
|
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
2643
2920
|
var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
|
|
2644
2921
|
var comboCache;
|
|
@@ -2679,8 +2956,8 @@ var sharedStyleDedupRule = {
|
|
|
2679
2956
|
}
|
|
2680
2957
|
},
|
|
2681
2958
|
create(context) {
|
|
2682
|
-
const sourceRoot2 =
|
|
2683
|
-
const file =
|
|
2959
|
+
const sourceRoot2 = path31.resolve("src");
|
|
2960
|
+
const file = path31.resolve(context.filename);
|
|
2684
2961
|
const segments = segmentsOf(file, sourceRoot2);
|
|
2685
2962
|
if (segments.length === 0) return {};
|
|
2686
2963
|
const index = getProjectIndex(sourceRoot2);
|
|
@@ -2702,28 +2979,111 @@ var sharedStyleDedupRule = {
|
|
|
2702
2979
|
}
|
|
2703
2980
|
};
|
|
2704
2981
|
|
|
2705
|
-
// eslint/pasika/rules/
|
|
2706
|
-
|
|
2982
|
+
// eslint/pasika/rules/repeated-structure.ts
|
|
2983
|
+
function nodeKind(node) {
|
|
2984
|
+
return node.type;
|
|
2985
|
+
}
|
|
2986
|
+
function isJsxNode(node) {
|
|
2987
|
+
const kind = nodeKind(node);
|
|
2988
|
+
return kind === "JSXElement" || kind === "JSXFragment";
|
|
2989
|
+
}
|
|
2990
|
+
function isJsxExpressionContainer(node) {
|
|
2991
|
+
return node.type === "JSXExpressionContainer";
|
|
2992
|
+
}
|
|
2993
|
+
function walkJsx(current, visit) {
|
|
2994
|
+
if (isJsxNode(current)) {
|
|
2995
|
+
visit(current);
|
|
2996
|
+
for (const child of current.children ?? []) walkJsx(child, visit);
|
|
2997
|
+
} else if (isJsxExpressionContainer(current)) {
|
|
2998
|
+
if (current.expression) walkJsx(current.expression, visit);
|
|
2999
|
+
} else if (current.type === "ArrowFunctionExpression") {
|
|
3000
|
+
walkJsx(current.body, visit);
|
|
3001
|
+
} else if (current.type === "CallExpression") {
|
|
3002
|
+
for (const argument of current.arguments) walkJsx(argument, visit);
|
|
3003
|
+
} else if (current.type === "ConditionalExpression") {
|
|
3004
|
+
walkJsx(current.consequent, visit);
|
|
3005
|
+
walkJsx(current.alternate, visit);
|
|
3006
|
+
} else if (current.type === "LogicalExpression") {
|
|
3007
|
+
walkJsx(current.left, visit);
|
|
3008
|
+
walkJsx(current.right, visit);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function signature(node) {
|
|
3012
|
+
const parts = [];
|
|
3013
|
+
const visit = (current) => {
|
|
3014
|
+
const opening = nodeKind(current) === "JSXFragment" ? "<>" : current.openingElement?.name.name ?? "<>";
|
|
3015
|
+
parts.push(opening);
|
|
3016
|
+
if (nodeKind(current) === "JSXElement") {
|
|
3017
|
+
const attributes = (current.openingElement?.attributes ?? []).filter((attribute) => attribute.type === "JSXAttribute").map((attribute) => {
|
|
3018
|
+
const name = attribute.name?.name ?? "";
|
|
3019
|
+
if (name === "className" && attribute.value?.type === "Literal" && typeof attribute.value.value === "string") {
|
|
3020
|
+
return `class="${attribute.value.value.split(/\s+/).filter(Boolean).sort().join(" ")}"`;
|
|
3021
|
+
}
|
|
3022
|
+
return name;
|
|
3023
|
+
}).sort();
|
|
3024
|
+
parts.push(`[${attributes.join(",")}]`);
|
|
3025
|
+
}
|
|
3026
|
+
for (const child of current.children ?? []) {
|
|
3027
|
+
if (isJsxNode(child)) {
|
|
3028
|
+
visit(child);
|
|
3029
|
+
} else if (isJsxExpressionContainer(child)) {
|
|
3030
|
+
walkJsx(child, visit);
|
|
3031
|
+
parts.push("{}");
|
|
3032
|
+
} else {
|
|
3033
|
+
parts.push("{}");
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3037
|
+
visit(node);
|
|
3038
|
+
return parts.join("/");
|
|
3039
|
+
}
|
|
3040
|
+
function isSubstantial(node) {
|
|
3041
|
+
let count = 0;
|
|
3042
|
+
walkJsx(node, () => {
|
|
3043
|
+
count += 1;
|
|
3044
|
+
});
|
|
3045
|
+
return count >= 3;
|
|
3046
|
+
}
|
|
3047
|
+
function blockChildren(node) {
|
|
3048
|
+
return (node.children ?? []).filter(isJsxNode);
|
|
3049
|
+
}
|
|
3050
|
+
var repeatedStructureRule = {
|
|
2707
3051
|
meta: {
|
|
2708
3052
|
schema: [],
|
|
2709
3053
|
type: "problem",
|
|
2710
3054
|
docs: {
|
|
2711
|
-
description: "
|
|
3055
|
+
description: "Require extracting a repeated block of elements as a named component."
|
|
2712
3056
|
}
|
|
2713
3057
|
},
|
|
2714
3058
|
create(context) {
|
|
3059
|
+
if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
|
|
2715
3060
|
return {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
loc: comment.loc,
|
|
2722
|
-
message: "Do not use eslint-disable directives; fix the reported violation instead."
|
|
2723
|
-
});
|
|
2724
|
-
}
|
|
3061
|
+
JSXElement(node) {
|
|
3062
|
+
checkChildren(node);
|
|
3063
|
+
},
|
|
3064
|
+
JSXFragment(node) {
|
|
3065
|
+
checkChildren(node);
|
|
2725
3066
|
}
|
|
2726
3067
|
};
|
|
3068
|
+
function checkChildren(node) {
|
|
3069
|
+
const children = blockChildren(node);
|
|
3070
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3071
|
+
for (const child of children) {
|
|
3072
|
+
const sig = signature(child);
|
|
3073
|
+
const group = seen.get(sig) ?? [];
|
|
3074
|
+
group.push(child);
|
|
3075
|
+
seen.set(sig, group);
|
|
3076
|
+
}
|
|
3077
|
+
for (const [, group] of seen) {
|
|
3078
|
+
if (group.length < 2) continue;
|
|
3079
|
+
const first = group[0];
|
|
3080
|
+
if (!first || !isSubstantial(first)) continue;
|
|
3081
|
+
context.report({
|
|
3082
|
+
node: first,
|
|
3083
|
+
message: `The same arrangement of elements appears ${String(group.length)} times here; extract it as a named component. Different data or labels do not prevent extraction. See docs/code-organization-guide/rules/repeated-structure-rule.md`
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
2727
3087
|
}
|
|
2728
3088
|
};
|
|
2729
3089
|
|
|
@@ -2801,7 +3161,7 @@ var zodSchemaValidationRule = {
|
|
|
2801
3161
|
};
|
|
2802
3162
|
|
|
2803
3163
|
// eslint/pasika/rules/source-under-src.ts
|
|
2804
|
-
import
|
|
3164
|
+
import path32 from "path";
|
|
2805
3165
|
var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
2806
3166
|
".agents",
|
|
2807
3167
|
".cache",
|
|
@@ -2842,14 +3202,14 @@ var sourceUnderSrcRule = {
|
|
|
2842
3202
|
}
|
|
2843
3203
|
},
|
|
2844
3204
|
create(context) {
|
|
2845
|
-
const filename =
|
|
3205
|
+
const filename = path32.resolve(context.filename);
|
|
2846
3206
|
if (!MODULE_EXTENSION.test(filename)) return {};
|
|
2847
|
-
const relative =
|
|
3207
|
+
const relative = path32.relative(process.cwd(), filename).replace(/\\/g, "/");
|
|
2848
3208
|
if (relative === "src" || relative.startsWith("src/")) return {};
|
|
2849
3209
|
const topLevel = relative.split("/")[0] ?? "";
|
|
2850
3210
|
if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
|
|
2851
3211
|
if (!relative.includes("/")) {
|
|
2852
|
-
const basename =
|
|
3212
|
+
const basename = path32.basename(filename);
|
|
2853
3213
|
if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
|
|
2854
3214
|
}
|
|
2855
3215
|
return {
|
|
@@ -2911,7 +3271,7 @@ var docKindSuffixRule = {
|
|
|
2911
3271
|
};
|
|
2912
3272
|
|
|
2913
3273
|
// eslint/pasika/rules/md/title-matches-file-name.ts
|
|
2914
|
-
import
|
|
3274
|
+
import path33 from "path";
|
|
2915
3275
|
function toExpectedFileName(title) {
|
|
2916
3276
|
return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
2917
3277
|
}
|
|
@@ -2931,7 +3291,7 @@ var titleMatchesFileNameRule = {
|
|
|
2931
3291
|
if (!filename.endsWith(".md")) return;
|
|
2932
3292
|
const title = getTextContent(node).trim();
|
|
2933
3293
|
const expectedFileName = toExpectedFileName(title);
|
|
2934
|
-
const actualFileName =
|
|
3294
|
+
const actualFileName = path33.basename(filename);
|
|
2935
3295
|
if (!title) {
|
|
2936
3296
|
context.report({
|
|
2937
3297
|
node,
|
|
@@ -3404,7 +3764,7 @@ var referenceBlockHeadingsRule = {
|
|
|
3404
3764
|
};
|
|
3405
3765
|
|
|
3406
3766
|
// eslint/pasika/rules/md/support-document-placement.ts
|
|
3407
|
-
import
|
|
3767
|
+
import path34 from "path";
|
|
3408
3768
|
var supportDocumentPlacementRule = {
|
|
3409
3769
|
meta: {
|
|
3410
3770
|
type: "problem",
|
|
@@ -3418,7 +3778,7 @@ var supportDocumentPlacementRule = {
|
|
|
3418
3778
|
root(node) {
|
|
3419
3779
|
const filename = getFilename(context);
|
|
3420
3780
|
if (!filename.endsWith(".md")) return;
|
|
3421
|
-
const parentFolder =
|
|
3781
|
+
const parentFolder = path34.basename(path34.dirname(filename));
|
|
3422
3782
|
if (filename.endsWith("-rule.md") && parentFolder !== "rules") {
|
|
3423
3783
|
context.report({
|
|
3424
3784
|
node,
|
|
@@ -3461,11 +3821,11 @@ var noTemplatePromptRule = {
|
|
|
3461
3821
|
};
|
|
3462
3822
|
|
|
3463
3823
|
// eslint/pasika/rules/md/guide-folder-entry-point.ts
|
|
3464
|
-
import
|
|
3824
|
+
import path36 from "path";
|
|
3465
3825
|
|
|
3466
3826
|
// eslint/pasika/rules/md/project-index.ts
|
|
3467
3827
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
|
|
3468
|
-
import
|
|
3828
|
+
import path35 from "path";
|
|
3469
3829
|
var KIND_BY_SUFFIX = [
|
|
3470
3830
|
["-rule.md", "rule"],
|
|
3471
3831
|
["-guide.md", "guide"],
|
|
@@ -3474,7 +3834,7 @@ var KIND_BY_SUFFIX = [
|
|
|
3474
3834
|
];
|
|
3475
3835
|
function listMarkdownFiles(dir) {
|
|
3476
3836
|
return readdirSync2(dir).flatMap((entry) => {
|
|
3477
|
-
const entryPath =
|
|
3837
|
+
const entryPath = path35.join(dir, entry);
|
|
3478
3838
|
if (statSync3(entryPath).isDirectory()) {
|
|
3479
3839
|
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
3480
3840
|
}
|
|
@@ -3492,11 +3852,11 @@ function getProjectDocs(docsRoot) {
|
|
|
3492
3852
|
if (cached) return cached;
|
|
3493
3853
|
const files = listMarkdownFiles(docsRoot);
|
|
3494
3854
|
const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
3495
|
-
const fileName =
|
|
3855
|
+
const fileName = path35.basename(filePath);
|
|
3496
3856
|
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
3497
3857
|
return {
|
|
3498
3858
|
filePath,
|
|
3499
|
-
doc:
|
|
3859
|
+
doc: path35.relative(docsRoot, filePath).split(path35.sep).join("/"),
|
|
3500
3860
|
fileName,
|
|
3501
3861
|
kind,
|
|
3502
3862
|
title: extractTitle(filePath)
|
|
@@ -3506,12 +3866,12 @@ function getProjectDocs(docsRoot) {
|
|
|
3506
3866
|
return docs;
|
|
3507
3867
|
}
|
|
3508
3868
|
function findDocsRoot(filePath) {
|
|
3509
|
-
let dir =
|
|
3869
|
+
let dir = path35.dirname(filePath);
|
|
3510
3870
|
for (; ; ) {
|
|
3511
|
-
if (
|
|
3871
|
+
if (path35.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
|
|
3512
3872
|
return dir;
|
|
3513
3873
|
}
|
|
3514
|
-
const parent =
|
|
3874
|
+
const parent = path35.dirname(dir);
|
|
3515
3875
|
if (parent === dir) return void 0;
|
|
3516
3876
|
dir = parent;
|
|
3517
3877
|
}
|
|
@@ -3535,13 +3895,13 @@ var guideFolderEntryPointRule = {
|
|
|
3535
3895
|
if (!docsRoot) return;
|
|
3536
3896
|
const docs = getProjectDocs(docsRoot);
|
|
3537
3897
|
const guideFolders = new Set(
|
|
3538
|
-
docs.filter((doc) => ["rules", "references"].includes(
|
|
3898
|
+
docs.filter((doc) => ["rules", "references"].includes(path36.basename(path36.dirname(doc.filePath)))).map((doc) => path36.dirname(path36.dirname(doc.filePath))).filter((folder) => path36.resolve(folder) !== path36.resolve(docsRoot))
|
|
3539
3899
|
);
|
|
3540
|
-
const currentDir =
|
|
3900
|
+
const currentDir = path36.dirname(filename);
|
|
3541
3901
|
if (guideFolders.has(currentDir)) {
|
|
3542
|
-
const expectedEntryPoint = `${
|
|
3902
|
+
const expectedEntryPoint = `${path36.basename(currentDir)}.md`;
|
|
3543
3903
|
const hasEntryPoint = docs.some(
|
|
3544
|
-
(doc) => doc.kind === "guide" &&
|
|
3904
|
+
(doc) => doc.kind === "guide" && path36.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
|
|
3545
3905
|
);
|
|
3546
3906
|
if (!hasEntryPoint) {
|
|
3547
3907
|
context.report({
|
|
@@ -3598,6 +3958,39 @@ var rfcOnlyInBulletsRule = {
|
|
|
3598
3958
|
}
|
|
3599
3959
|
};
|
|
3600
3960
|
|
|
3961
|
+
// eslint/pasika/rules/md/rfc-keyword-in-every-bullet.ts
|
|
3962
|
+
function checkBullets(node, context) {
|
|
3963
|
+
if (node.type === "listItem" && !containsRfcKeyword(getTextContent(node))) {
|
|
3964
|
+
context.report({
|
|
3965
|
+
node,
|
|
3966
|
+
message: "bullet point contains no RFC 2119 keyword"
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3969
|
+
if ("children" in node) {
|
|
3970
|
+
for (const child of node.children) {
|
|
3971
|
+
checkBullets(child, context);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
var rfcKeywordInEveryBulletRule = {
|
|
3976
|
+
meta: {
|
|
3977
|
+
type: "problem",
|
|
3978
|
+
docs: {
|
|
3979
|
+
description: "Every bullet in a rule or policy document must contain an RFC 2119 keyword.",
|
|
3980
|
+
recommended: true
|
|
3981
|
+
}
|
|
3982
|
+
},
|
|
3983
|
+
create(context) {
|
|
3984
|
+
return {
|
|
3985
|
+
root(node) {
|
|
3986
|
+
const filename = getFilename(context);
|
|
3987
|
+
if (!filename.endsWith("-rule.md") && !filename.endsWith("-policy.md")) return;
|
|
3988
|
+
checkBullets(node, context);
|
|
3989
|
+
}
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
|
|
3601
3994
|
// eslint/pasika/rules/md/policy-subject-headings.ts
|
|
3602
3995
|
var policySubjectHeadingsRule = {
|
|
3603
3996
|
meta: {
|
|
@@ -3733,7 +4126,7 @@ var noNestedHowToRule = {
|
|
|
3733
4126
|
|
|
3734
4127
|
// eslint/pasika/rules/md/glossary-term-linking.ts
|
|
3735
4128
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3736
|
-
import
|
|
4129
|
+
import path37 from "path";
|
|
3737
4130
|
function extractGlossaryTerms(filePath) {
|
|
3738
4131
|
const content = readFileSync5(filePath, "utf8");
|
|
3739
4132
|
const terms = [];
|
|
@@ -3787,9 +4180,9 @@ var glossaryTermLinkingRule = {
|
|
|
3787
4180
|
const docsRoot = findDocsRoot(filename);
|
|
3788
4181
|
if (!docsRoot) return;
|
|
3789
4182
|
const docs = getProjectDocs(docsRoot);
|
|
3790
|
-
const guideDir =
|
|
4183
|
+
const guideDir = path37.dirname(filename);
|
|
3791
4184
|
const guideReferences = docs.filter(
|
|
3792
|
-
(doc) => doc.kind === "reference" &&
|
|
4185
|
+
(doc) => doc.kind === "reference" && path37.dirname(doc.filePath) === guideDir
|
|
3793
4186
|
);
|
|
3794
4187
|
if (guideReferences.length === 0) return;
|
|
3795
4188
|
const glossaryTerms = [];
|
|
@@ -3833,6 +4226,7 @@ var mdRules = {
|
|
|
3833
4226
|
"no-template-prompt": noTemplatePromptRule,
|
|
3834
4227
|
"guide-folder-entry-point": guideFolderEntryPointRule,
|
|
3835
4228
|
"rfc-only-in-bullets": rfcOnlyInBulletsRule,
|
|
4229
|
+
"rfc-keyword-in-every-bullet": rfcKeywordInEveryBulletRule,
|
|
3836
4230
|
"policy-subject-headings": policySubjectHeadingsRule,
|
|
3837
4231
|
"guide-link-anchors": guideLinkAnchorsRule,
|
|
3838
4232
|
"no-nested-how-to": noNestedHowToRule,
|
|
@@ -3862,7 +4256,7 @@ function atrules(node) {
|
|
|
3862
4256
|
function atrulesNamed(node, name) {
|
|
3863
4257
|
return atrules(node).filter((candidate) => candidate.type === "Atrule" && candidate.name === name);
|
|
3864
4258
|
}
|
|
3865
|
-
function
|
|
4259
|
+
function blockChildren2(node) {
|
|
3866
4260
|
if (!node) return [];
|
|
3867
4261
|
if (!("block" in node) || !node.block) return [];
|
|
3868
4262
|
return node.block.children;
|
|
@@ -3909,7 +4303,7 @@ var themeResetRule = {
|
|
|
3909
4303
|
return {
|
|
3910
4304
|
"StyleSheet:exit"(node) {
|
|
3911
4305
|
const resetFound = atrulesNamed(node, "theme").some(
|
|
3912
|
-
(theme) =>
|
|
4306
|
+
(theme) => blockChildren2(theme).some((child) => {
|
|
3913
4307
|
if (child.type === "Declaration") return child.property.startsWith("--*");
|
|
3914
4308
|
if (child.type === "Raw") return child.value.includes("--*: initial");
|
|
3915
4309
|
return false;
|
|
@@ -3940,7 +4334,7 @@ var rootVariablesRule = {
|
|
|
3940
4334
|
"StyleSheet:exit"(node) {
|
|
3941
4335
|
const hasRootVars = rules(node).some((rule) => {
|
|
3942
4336
|
if (!selectorNames(rule).includes("root")) return false;
|
|
3943
|
-
return
|
|
4337
|
+
return blockChildren2(rule).some((child) => child.type === "Declaration");
|
|
3944
4338
|
});
|
|
3945
4339
|
if (!hasRootVars) {
|
|
3946
4340
|
context.report({
|
|
@@ -3949,7 +4343,7 @@ var rootVariablesRule = {
|
|
|
3949
4343
|
});
|
|
3950
4344
|
}
|
|
3951
4345
|
const referencingThemes = atrulesNamed(node, "theme").filter(
|
|
3952
|
-
(theme) =>
|
|
4346
|
+
(theme) => blockChildren2(theme).some((child) => {
|
|
3953
4347
|
if (child.type === "Raw") return child.value.includes("var(");
|
|
3954
4348
|
if (child.type === "Declaration") return JSON.stringify(child.value).includes("var(");
|
|
3955
4349
|
return false;
|
|
@@ -3983,9 +4377,9 @@ var applyUsageRule = {
|
|
|
3983
4377
|
"StyleSheet:exit"(node) {
|
|
3984
4378
|
const layers = node.children.filter((child) => child.type === "Atrule" && child.name === "layer");
|
|
3985
4379
|
for (const layer of layers) {
|
|
3986
|
-
for (const child of
|
|
4380
|
+
for (const child of blockChildren2(layer)) {
|
|
3987
4381
|
if (child.type !== "Rule") continue;
|
|
3988
|
-
for (const declaration of
|
|
4382
|
+
for (const declaration of blockChildren2(child)) {
|
|
3989
4383
|
if (declaration.type !== "Declaration") continue;
|
|
3990
4384
|
context.report({
|
|
3991
4385
|
node: declaration,
|
|
@@ -4015,10 +4409,10 @@ var baseLayerPairRule = {
|
|
|
4015
4409
|
if (baseLayers.length === 0) return;
|
|
4016
4410
|
const applied = [];
|
|
4017
4411
|
for (const layer of baseLayers) {
|
|
4018
|
-
for (const child of
|
|
4412
|
+
for (const child of blockChildren2(layer)) {
|
|
4019
4413
|
if (child.type !== "Rule") continue;
|
|
4020
4414
|
if (!selectorNames(child).includes("body")) continue;
|
|
4021
|
-
for (const declaration of
|
|
4415
|
+
for (const declaration of blockChildren2(child)) {
|
|
4022
4416
|
if (declaration.type !== "Atrule" || declaration.name !== "apply") continue;
|
|
4023
4417
|
applied.push(...preludeIdentifiers(declaration));
|
|
4024
4418
|
}
|
|
@@ -4143,7 +4537,7 @@ var customUtilityApplyRule = {
|
|
|
4143
4537
|
return {
|
|
4144
4538
|
"StyleSheet:exit"(node) {
|
|
4145
4539
|
for (const utility of atrulesNamed(node, "utility")) {
|
|
4146
|
-
for (const child of
|
|
4540
|
+
for (const child of blockChildren2(utility)) {
|
|
4147
4541
|
if (child.type === "Atrule" && child.name === "apply") continue;
|
|
4148
4542
|
if (child.type === "Declaration") {
|
|
4149
4543
|
context.report({
|
|
@@ -4215,7 +4609,7 @@ var themeVariableNamespaceRule = {
|
|
|
4215
4609
|
"StyleSheet:exit"(node) {
|
|
4216
4610
|
const namespaces = /* @__PURE__ */ new Set();
|
|
4217
4611
|
for (const theme of atrulesNamed(node, "theme")) {
|
|
4218
|
-
for (const child of
|
|
4612
|
+
for (const child of blockChildren2(theme)) {
|
|
4219
4613
|
if (child.type !== "Declaration") continue;
|
|
4220
4614
|
const segments = child.property.replace(/^--/, "").split("-");
|
|
4221
4615
|
if (segments[0]) namespaces.add(segments[0]);
|
|
@@ -4285,43 +4679,8 @@ var cssRules = {
|
|
|
4285
4679
|
"global-css-location": globalCssLocationRule
|
|
4286
4680
|
};
|
|
4287
4681
|
|
|
4288
|
-
// eslint/pasika/rules/json/no-cache-flag.ts
|
|
4289
|
-
function memberName(member) {
|
|
4290
|
-
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
4291
|
-
}
|
|
4292
|
-
var noCacheFlagRule = {
|
|
4293
|
-
meta: {
|
|
4294
|
-
schema: [],
|
|
4295
|
-
type: "problem",
|
|
4296
|
-
docs: {
|
|
4297
|
-
description: "Require lint scripts to not pass ESLint's --cache flag."
|
|
4298
|
-
}
|
|
4299
|
-
},
|
|
4300
|
-
create(context) {
|
|
4301
|
-
return {
|
|
4302
|
-
Document(node) {
|
|
4303
|
-
const root = node.body;
|
|
4304
|
-
if (root.type !== "Object") return;
|
|
4305
|
-
const scripts = root.members.find((member) => memberName(member) === "scripts");
|
|
4306
|
-
if (scripts?.value.type !== "Object") return;
|
|
4307
|
-
for (const member of scripts.value.members) {
|
|
4308
|
-
const name = memberName(member);
|
|
4309
|
-
if (name !== "lint" && name !== "fix") continue;
|
|
4310
|
-
if (member.value.type !== "String") continue;
|
|
4311
|
-
if (member.value.value.includes("--cache")) {
|
|
4312
|
-
context.report({
|
|
4313
|
-
node: member,
|
|
4314
|
-
message: "Lint scripts must not pass ESLint's --cache flag because cross-file rules require every file in the run."
|
|
4315
|
-
});
|
|
4316
|
-
}
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
};
|
|
4320
|
-
}
|
|
4321
|
-
};
|
|
4322
|
-
|
|
4323
4682
|
// eslint/pasika/rules/json/no-vulyk-dependency.ts
|
|
4324
|
-
function
|
|
4683
|
+
function memberName(member) {
|
|
4325
4684
|
return member.name.type === "String" ? member.name.value : member.name.name;
|
|
4326
4685
|
}
|
|
4327
4686
|
var noVulykDependencyRule = {
|
|
@@ -4338,9 +4697,9 @@ var noVulykDependencyRule = {
|
|
|
4338
4697
|
const root = node.body;
|
|
4339
4698
|
if (root.type !== "Object") return;
|
|
4340
4699
|
for (const key of ["dependencies", "devDependencies"]) {
|
|
4341
|
-
const section = root.members.find((member) =>
|
|
4700
|
+
const section = root.members.find((member) => memberName(member) === key);
|
|
4342
4701
|
if (section?.value.type !== "Object") continue;
|
|
4343
|
-
const vulyk = section.value.members.find((member) =>
|
|
4702
|
+
const vulyk = section.value.members.find((member) => memberName(member) === "vulyk");
|
|
4344
4703
|
if (vulyk) {
|
|
4345
4704
|
context.report({
|
|
4346
4705
|
node: vulyk,
|
|
@@ -4355,7 +4714,6 @@ var noVulykDependencyRule = {
|
|
|
4355
4714
|
|
|
4356
4715
|
// eslint/pasika/rules/json/index.ts
|
|
4357
4716
|
var jsonRules = {
|
|
4358
|
-
"no-cache-flag": noCacheFlagRule,
|
|
4359
4717
|
"no-vulyk-dependency": noVulykDependencyRule
|
|
4360
4718
|
};
|
|
4361
4719
|
|
|
@@ -4394,8 +4752,10 @@ var pasikaRules = {
|
|
|
4394
4752
|
"stay-flat": stayFlatRule,
|
|
4395
4753
|
"type-extraction": typeExtractionRule,
|
|
4396
4754
|
"locale-placement": localePlacementRule,
|
|
4755
|
+
"sole-state-owner": soleStateOwnerRule,
|
|
4756
|
+
"locale-key-shape": localeKeyShapeRule,
|
|
4397
4757
|
"shared-style-dedup": sharedStyleDedupRule,
|
|
4398
|
-
"
|
|
4758
|
+
"repeated-structure": repeatedStructureRule,
|
|
4399
4759
|
"zod-schema-validation": zodSchemaValidationRule,
|
|
4400
4760
|
"source-under-src": sourceUnderSrcRule
|
|
4401
4761
|
};
|