pasika 0.10.85 → 0.10.86
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/dist/eslint/pasika/index.js +283 -260
- package/package.json +1 -1
|
@@ -164,7 +164,7 @@ var apiContractRule = {
|
|
|
164
164
|
|
|
165
165
|
// eslint/rules/application-structure.ts
|
|
166
166
|
import fs2 from "fs";
|
|
167
|
-
import
|
|
167
|
+
import path6 from "path";
|
|
168
168
|
|
|
169
169
|
// eslint/project/ccf.ts
|
|
170
170
|
import fs from "fs";
|
|
@@ -494,264 +494,8 @@ function parseModule(file) {
|
|
|
494
494
|
return { file: path3.resolve(file), imports, reexports, exports, namedNumericLiterals };
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
// eslint/rules/project-root.ts
|
|
498
|
-
import path4 from "path";
|
|
499
|
-
function sourceRootOf(context) {
|
|
500
|
-
return path4.resolve(context.cwd ?? process.cwd(), "src");
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// eslint/rules/application-structure.ts
|
|
504
|
-
var MODULE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
505
|
-
var ROUTING_FILES = /* @__PURE__ */ new Set([
|
|
506
|
-
"default",
|
|
507
|
-
"error",
|
|
508
|
-
"global-error",
|
|
509
|
-
"instrumentation",
|
|
510
|
-
"layout",
|
|
511
|
-
"loading",
|
|
512
|
-
"middleware",
|
|
513
|
-
"not-found",
|
|
514
|
-
"page",
|
|
515
|
-
"route",
|
|
516
|
-
"template",
|
|
517
|
-
// File conventions Next.js requires to keep their exact names in src/app/
|
|
518
|
-
"apple-icon",
|
|
519
|
-
"icon",
|
|
520
|
-
"manifest",
|
|
521
|
-
"opengraph-image",
|
|
522
|
-
"robots",
|
|
523
|
-
"sitemap",
|
|
524
|
-
"twitter-image"
|
|
525
|
-
]);
|
|
526
|
-
var EXPECTED_SUPPORT_FOLDER = {
|
|
527
|
-
hook: "hooks",
|
|
528
|
-
type: "types",
|
|
529
|
-
schema: "schemas",
|
|
530
|
-
constant: "constants",
|
|
531
|
-
function: "utils"
|
|
532
|
-
};
|
|
533
|
-
function report(context, message) {
|
|
534
|
-
return {
|
|
535
|
-
Program(node) {
|
|
536
|
-
context.report({ node, loc: { line: 1, column: 0 }, message });
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
function isCodeFile(filename) {
|
|
541
|
-
return MODULE_EXTENSIONS.has(path5.extname(filename));
|
|
542
|
-
}
|
|
543
|
-
function isRootSupportFolder(folder) {
|
|
544
|
-
return SUPPORT_FOLDERS.has(folder);
|
|
545
|
-
}
|
|
546
|
-
function exportedKinds(filename) {
|
|
547
|
-
try {
|
|
548
|
-
return new Set(parseModule(filename).exports.map((moduleExport) => moduleExport.kind));
|
|
549
|
-
} catch {
|
|
550
|
-
return /* @__PURE__ */ new Set();
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function expectedSupportFolder(kinds) {
|
|
554
|
-
for (const kind of ["component", "hook", "schema", "function", "constant", "type"]) {
|
|
555
|
-
if (kinds.has(kind)) return EXPECTED_SUPPORT_FOLDER[kind];
|
|
556
|
-
}
|
|
557
|
-
return void 0;
|
|
558
|
-
}
|
|
559
|
-
function mixedSupportKinds(folder, kinds) {
|
|
560
|
-
if (folder === "types") return kinds.has("type") && (kinds.has("constant") || kinds.has("schema"));
|
|
561
|
-
if (folder === "schemas") return kinds.has("schema") && (kinds.has("constant") || kinds.has("type"));
|
|
562
|
-
if (folder === "constants") return kinds.has("constant") && kinds.has("schema");
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
function configModuleRoot(filename, sourceRoot) {
|
|
566
|
-
const segments = segmentsOf(filename, sourceRoot);
|
|
567
|
-
if (segments[0] !== "config" || segments.length < 3) return void 0;
|
|
568
|
-
return path5.join(sourceRoot, "config", segments[1] ?? "");
|
|
569
|
-
}
|
|
570
|
-
function componentFolderStart(segments) {
|
|
571
|
-
if (segments[0] === "features") return 2;
|
|
572
|
-
if (segments[0] === "compositions" || segments[0] === "shared") return 1;
|
|
573
|
-
return -1;
|
|
574
|
-
}
|
|
575
|
-
function hasComponentBarrel(folderPath) {
|
|
576
|
-
return hasExactEntry(folderPath, "index.ts");
|
|
577
|
-
}
|
|
578
|
-
function componentFolderViolation(segments, sourceRoot) {
|
|
579
|
-
const start = componentFolderStart(segments);
|
|
580
|
-
if (start < 0) return void 0;
|
|
581
|
-
for (let depth = segments.length - 2; depth >= start; depth -= 1) {
|
|
582
|
-
const folder = segments[depth];
|
|
583
|
-
if (!folder || SUPPORT_FOLDERS.has(folder)) continue;
|
|
584
|
-
const folderPath = path5.join(sourceRoot, ...segments.slice(0, depth + 1));
|
|
585
|
-
const label = `src/${segments.slice(0, depth + 1).join("/")}/`;
|
|
586
|
-
if (!hasComponentOwner(folderPath, folder)) {
|
|
587
|
-
return `A folder that is not a support folder must be a component folder; add "${folder}.tsx" to ${label} or move its files into a support folder.`;
|
|
588
|
-
}
|
|
589
|
-
if (!hasComponentBarrel(folderPath)) {
|
|
590
|
-
return `A component folder must have an index.ts that named-re-exports its component; add index.ts to ${label}.`;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return void 0;
|
|
594
|
-
}
|
|
595
|
-
var applicationStructureRule = {
|
|
596
|
-
meta: {
|
|
597
|
-
schema: [],
|
|
598
|
-
type: "problem",
|
|
599
|
-
docs: {
|
|
600
|
-
description: "Enforce the repository's application, configuration, and support-folder structure."
|
|
601
|
-
}
|
|
602
|
-
},
|
|
603
|
-
create(context) {
|
|
604
|
-
const filename = path5.resolve(context.filename);
|
|
605
|
-
const sourceRoot = sourceRootOf(context);
|
|
606
|
-
const segments = segmentsOf(filename, sourceRoot);
|
|
607
|
-
if (segments.length === 0) return {};
|
|
608
|
-
const [topLevel, secondLevel] = segments;
|
|
609
|
-
if (topLevel === void 0) return {};
|
|
610
|
-
if (segments.length > 1 && topLevel !== "app" && topLevel !== "compositions" && topLevel !== "features" && topLevel !== "shared" && topLevel !== "config" && topLevel !== "locales" && !isRootSupportFolder(topLevel)) {
|
|
611
|
-
return report(
|
|
612
|
-
context,
|
|
613
|
-
`Move this source file under an allowed src/ folder; "src/${topLevel}/" is not part of the application structure.`
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
if (topLevel === "features" && segments.length >= 2 && secondLevel !== void 0) {
|
|
617
|
-
if (SUPPORT_FOLDERS.has(secondLevel)) {
|
|
618
|
-
return report(
|
|
619
|
-
context,
|
|
620
|
-
`Move this support folder inside a feature folder; "src/features/${secondLevel}/" does not belong to a feature.`
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
if (segments.length === 2) {
|
|
624
|
-
return report(context, "src/features/ must contain only feature folders, not source files.");
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (topLevel === "config") {
|
|
628
|
-
if (segments.length === 2) {
|
|
629
|
-
return report(
|
|
630
|
-
context,
|
|
631
|
-
"A configuration module must be a src/config/<config-name>/ folder with index.ts as its entry point."
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
const moduleRoot = configModuleRoot(filename, sourceRoot);
|
|
635
|
-
if (moduleRoot && !fs2.existsSync(path5.join(moduleRoot, "index.ts"))) {
|
|
636
|
-
return report(
|
|
637
|
-
context,
|
|
638
|
-
`Add src/config/${path5.basename(moduleRoot)}/index.ts as the configuration module entry point.`
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
if (moduleRoot && segments.length === 3 && path5.basename(filename) !== "index.ts") {
|
|
642
|
-
const kinds2 = exportedKinds(filename);
|
|
643
|
-
const expected2 = expectedSupportFolder(kinds2);
|
|
644
|
-
if (expected2 !== void 0) {
|
|
645
|
-
return report(
|
|
646
|
-
context,
|
|
647
|
-
`Move this configuration support file into src/config/${path5.basename(moduleRoot)}/${expected2}/.`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (topLevel === "app" && isCodeFile(filename)) {
|
|
653
|
-
const basename = path5.basename(filename, path5.extname(filename));
|
|
654
|
-
const currentFolder2 = path5.basename(path5.dirname(filename));
|
|
655
|
-
if (SUPPORT_FOLDERS.has(currentFolder2)) {
|
|
656
|
-
return report(
|
|
657
|
-
context,
|
|
658
|
-
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
if (!ROUTING_FILES.has(basename) && path5.extname(filename) !== ".css") {
|
|
662
|
-
return report(
|
|
663
|
-
context,
|
|
664
|
-
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
if (ROUTING_FILES.has(basename) || path5.extname(filename) === ".css") return {};
|
|
668
|
-
}
|
|
669
|
-
const currentFolder = path5.basename(path5.dirname(filename));
|
|
670
|
-
const kinds = exportedKinds(filename);
|
|
671
|
-
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path5.basename(filename) === "index.ts";
|
|
672
|
-
if (isConfigModuleRoot) return {};
|
|
673
|
-
if (!SUPPORT_FOLDERS.has(currentFolder)) {
|
|
674
|
-
const expected2 = expectedSupportFolder(kinds);
|
|
675
|
-
if (expected2 !== void 0 && segments.length > 2) {
|
|
676
|
-
return report(
|
|
677
|
-
context,
|
|
678
|
-
`Move this file to a ${expected2}/ folder; ${currentFolder}/ is not a recognized support folder.`
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
const violation2 = componentFolderViolation(segments, sourceRoot);
|
|
682
|
-
if (violation2 !== void 0) return report(context, violation2);
|
|
683
|
-
return {};
|
|
684
|
-
}
|
|
685
|
-
if (kinds.has("component")) {
|
|
686
|
-
return report(
|
|
687
|
-
context,
|
|
688
|
-
`A support folder must not contain a component; move ${path5.basename(filename)} beside ${currentFolder}/.`
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
if (mixedSupportKinds(currentFolder, kinds)) {
|
|
692
|
-
return report(
|
|
693
|
-
context,
|
|
694
|
-
`A ${currentFolder}/ folder must not mix unrelated support kinds; keep types, schemas, and constants in their matching folders.`
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
const expected = expectedSupportFolder(kinds);
|
|
698
|
-
if (expected !== void 0 && expected !== currentFolder) {
|
|
699
|
-
return report(
|
|
700
|
-
context,
|
|
701
|
-
`Move this file to a ${expected}/ folder; ${currentFolder}/ is reserved for ${expected === "utils" ? "utilities" : expected}.`
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
const violation = componentFolderViolation(segments, sourceRoot);
|
|
705
|
-
if (violation !== void 0) return report(context, violation);
|
|
706
|
-
return {};
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
// eslint/rules/cn-helper.ts
|
|
711
|
-
var HELPER2 = "cn";
|
|
712
|
-
var ENTRY2 = "pasika/cn";
|
|
713
|
-
var DOC2 = "docs/pasika-adoption-guide/rules/cn-helper-rule.md";
|
|
714
|
-
var DECLARED2 = `A file must not declare its own ${HELPER2}; import it from ${ENTRY2}. See ${DOC2}`;
|
|
715
|
-
var IMPORTED2 = `${HELPER2} must be imported from ${ENTRY2}. See ${DOC2}`;
|
|
716
|
-
function isNamed2(node, name) {
|
|
717
|
-
return node?.type === "Identifier" && node.name === name;
|
|
718
|
-
}
|
|
719
|
-
var cnHelperRule = {
|
|
720
|
-
meta: {
|
|
721
|
-
schema: [],
|
|
722
|
-
type: "problem",
|
|
723
|
-
docs: {
|
|
724
|
-
description: `Require ${HELPER2} to be imported from ${ENTRY2} rather than declared in the repository.`
|
|
725
|
-
}
|
|
726
|
-
},
|
|
727
|
-
create(context) {
|
|
728
|
-
const checkSource = (node) => {
|
|
729
|
-
if (!node.source || node.source.value === ENTRY2) return;
|
|
730
|
-
for (const specifier of node.specifiers) {
|
|
731
|
-
if (!isNamed2(specifier.local, HELPER2)) continue;
|
|
732
|
-
context.report({ node: specifier, message: IMPORTED2 });
|
|
733
|
-
}
|
|
734
|
-
};
|
|
735
|
-
return {
|
|
736
|
-
FunctionDeclaration(node) {
|
|
737
|
-
if (isNamed2(node.id, HELPER2)) context.report({ node, message: DECLARED2 });
|
|
738
|
-
},
|
|
739
|
-
VariableDeclarator(node) {
|
|
740
|
-
if (isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) {
|
|
741
|
-
context.report({ node, message: DECLARED2 });
|
|
742
|
-
}
|
|
743
|
-
},
|
|
744
|
-
ImportDeclaration: checkSource,
|
|
745
|
-
ExportNamedDeclaration: checkSource
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// eslint/rules/component-casing.ts
|
|
751
|
-
import path7 from "path";
|
|
752
|
-
|
|
753
497
|
// eslint/rules/component-conventions.ts
|
|
754
|
-
import
|
|
498
|
+
import path4 from "path";
|
|
755
499
|
import ts2 from "typescript";
|
|
756
500
|
var isPascalCase2 = (name) => /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
757
501
|
var isHookCallName = (name) => /^use[A-Z]/.test(name);
|
|
@@ -868,7 +612,7 @@ function componentFromVariable(node) {
|
|
|
868
612
|
};
|
|
869
613
|
}
|
|
870
614
|
function parseComponentInfo(text, filename, options) {
|
|
871
|
-
const sourceFile = ts2.createSourceFile(
|
|
615
|
+
const sourceFile = ts2.createSourceFile(path4.resolve(filename), text, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
872
616
|
const components = [];
|
|
873
617
|
const namedExports = namedExportedLocalNames(sourceFile);
|
|
874
618
|
for (const statement of sourceFile.statements) {
|
|
@@ -892,7 +636,7 @@ function parseComponentInfo(text, filename, options) {
|
|
|
892
636
|
return components;
|
|
893
637
|
}
|
|
894
638
|
function findJsxReturningDeclarations(text, filename) {
|
|
895
|
-
const sourceFile = ts2.createSourceFile(
|
|
639
|
+
const sourceFile = ts2.createSourceFile(path4.resolve(filename), text, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
896
640
|
const results = [];
|
|
897
641
|
const record = (identifier, body) => {
|
|
898
642
|
if (isHookCallName(identifier.text) || !containsJsx(body)) return;
|
|
@@ -996,7 +740,286 @@ function findRenderedComponentMarkerPaths(component) {
|
|
|
996
740
|
});
|
|
997
741
|
}
|
|
998
742
|
|
|
743
|
+
// eslint/rules/project-root.ts
|
|
744
|
+
import path5 from "path";
|
|
745
|
+
function sourceRootOf(context) {
|
|
746
|
+
return path5.resolve(context.cwd ?? process.cwd(), "src");
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// eslint/rules/application-structure.ts
|
|
750
|
+
var MODULE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
751
|
+
var ROUTING_FILES = /* @__PURE__ */ new Set([
|
|
752
|
+
"default",
|
|
753
|
+
"error",
|
|
754
|
+
"global-error",
|
|
755
|
+
"instrumentation",
|
|
756
|
+
"layout",
|
|
757
|
+
"loading",
|
|
758
|
+
"middleware",
|
|
759
|
+
"not-found",
|
|
760
|
+
"page",
|
|
761
|
+
"route",
|
|
762
|
+
"template",
|
|
763
|
+
// File conventions Next.js requires to keep their exact names in src/app/
|
|
764
|
+
"apple-icon",
|
|
765
|
+
"icon",
|
|
766
|
+
"manifest",
|
|
767
|
+
"opengraph-image",
|
|
768
|
+
"robots",
|
|
769
|
+
"sitemap",
|
|
770
|
+
"twitter-image"
|
|
771
|
+
]);
|
|
772
|
+
var EXPECTED_SUPPORT_FOLDER = {
|
|
773
|
+
hook: "hooks",
|
|
774
|
+
type: "types",
|
|
775
|
+
schema: "schemas",
|
|
776
|
+
constant: "constants",
|
|
777
|
+
function: "utils"
|
|
778
|
+
};
|
|
779
|
+
function report(context, message) {
|
|
780
|
+
return {
|
|
781
|
+
Program(node) {
|
|
782
|
+
context.report({ node, loc: { line: 1, column: 0 }, message });
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function isCodeFile(filename) {
|
|
787
|
+
return MODULE_EXTENSIONS.has(path6.extname(filename));
|
|
788
|
+
}
|
|
789
|
+
function isRootSupportFolder(folder) {
|
|
790
|
+
return SUPPORT_FOLDERS.has(folder);
|
|
791
|
+
}
|
|
792
|
+
function exportedKinds(filename) {
|
|
793
|
+
try {
|
|
794
|
+
return new Set(parseModule(filename).exports.map((moduleExport) => moduleExport.kind));
|
|
795
|
+
} catch {
|
|
796
|
+
return /* @__PURE__ */ new Set();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function expectedSupportFolder(kinds) {
|
|
800
|
+
for (const kind of ["component", "hook", "schema", "function", "constant", "type"]) {
|
|
801
|
+
if (kinds.has(kind)) return EXPECTED_SUPPORT_FOLDER[kind];
|
|
802
|
+
}
|
|
803
|
+
return void 0;
|
|
804
|
+
}
|
|
805
|
+
function mixedSupportKinds(folder, kinds) {
|
|
806
|
+
if (folder === "types") return kinds.has("type") && (kinds.has("constant") || kinds.has("schema"));
|
|
807
|
+
if (folder === "schemas") return kinds.has("schema") && (kinds.has("constant") || kinds.has("type"));
|
|
808
|
+
if (folder === "constants") return kinds.has("constant") && kinds.has("schema");
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
function configModuleRoot(filename, sourceRoot) {
|
|
812
|
+
const segments = segmentsOf(filename, sourceRoot);
|
|
813
|
+
if (segments[0] !== "config" || segments.length < 3) return void 0;
|
|
814
|
+
return path6.join(sourceRoot, "config", segments[1] ?? "");
|
|
815
|
+
}
|
|
816
|
+
function componentFolderStart(segments) {
|
|
817
|
+
if (segments[0] === "features") return 2;
|
|
818
|
+
if (segments[0] === "compositions" || segments[0] === "shared") return 1;
|
|
819
|
+
return -1;
|
|
820
|
+
}
|
|
821
|
+
function hasComponentBarrel(folderPath) {
|
|
822
|
+
return hasExactEntry(folderPath, "index.ts");
|
|
823
|
+
}
|
|
824
|
+
function componentFolderViolation(segments, sourceRoot) {
|
|
825
|
+
const start = componentFolderStart(segments);
|
|
826
|
+
if (start < 0) return void 0;
|
|
827
|
+
for (let depth = segments.length - 2; depth >= start; depth -= 1) {
|
|
828
|
+
const folder = segments[depth];
|
|
829
|
+
if (!folder || SUPPORT_FOLDERS.has(folder)) continue;
|
|
830
|
+
const folderPath = path6.join(sourceRoot, ...segments.slice(0, depth + 1));
|
|
831
|
+
const label = `src/${segments.slice(0, depth + 1).join("/")}/`;
|
|
832
|
+
if (!hasComponentOwner(folderPath, folder)) {
|
|
833
|
+
return `A folder that is not a support folder must be a component folder; add "${folder}.tsx" to ${label} or move its files into a support folder.`;
|
|
834
|
+
}
|
|
835
|
+
if (!hasComponentBarrel(folderPath)) {
|
|
836
|
+
return `A component folder must have an index.ts that named-re-exports its component; add index.ts to ${label}.`;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return void 0;
|
|
840
|
+
}
|
|
841
|
+
function componentFacadeTarget(context, filename) {
|
|
842
|
+
if (path6.extname(filename) !== ".ts" || path6.basename(filename) === "index.ts") return void 0;
|
|
843
|
+
const statements = context.sourceCode.ast.body;
|
|
844
|
+
if (statements.length === 0 || statements.some((node) => node.type !== "ExportNamedDeclaration" || !node.source)) {
|
|
845
|
+
return void 0;
|
|
846
|
+
}
|
|
847
|
+
const exports = statements.flatMap((node) => node.type === "ExportNamedDeclaration" ? node.specifiers : []);
|
|
848
|
+
const sources = statements.map((node) => node.type === "ExportNamedDeclaration" ? node.source?.value : void 0);
|
|
849
|
+
if (new Set(sources).size !== 1 || typeof sources[0] !== "string" || !sources[0].startsWith("./")) {
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
852
|
+
const componentFile = path6.resolve(path6.dirname(filename), `${sources[0]}.tsx`);
|
|
853
|
+
if (!fs2.existsSync(componentFile)) return void 0;
|
|
854
|
+
const components = parseComponentInfo(fs2.readFileSync(componentFile, "utf8"), componentFile);
|
|
855
|
+
return components.some(
|
|
856
|
+
({ name }) => exports.some((specifier) => specifier.local.type === "Identifier" && specifier.local.name === name)
|
|
857
|
+
) ? componentFile : void 0;
|
|
858
|
+
}
|
|
859
|
+
var applicationStructureRule = {
|
|
860
|
+
meta: {
|
|
861
|
+
schema: [],
|
|
862
|
+
type: "problem",
|
|
863
|
+
docs: {
|
|
864
|
+
description: "Enforce the repository's application, configuration, and support-folder structure."
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
create(context) {
|
|
868
|
+
const filename = path6.resolve(context.filename);
|
|
869
|
+
const sourceRoot = sourceRootOf(context);
|
|
870
|
+
const segments = segmentsOf(filename, sourceRoot);
|
|
871
|
+
if (segments.length === 0) return {};
|
|
872
|
+
const componentTarget = componentFacadeTarget(context, filename);
|
|
873
|
+
if (componentTarget) {
|
|
874
|
+
return report(
|
|
875
|
+
context,
|
|
876
|
+
`Avoid a forwarding .ts file for a sibling component: move the implementation from ${path6.basename(componentTarget)} to ${path6.basename(filename, ".ts")}.tsx and remove the .ts facade.`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
const [topLevel, secondLevel] = segments;
|
|
880
|
+
if (topLevel === void 0) return {};
|
|
881
|
+
if (segments.length > 1 && topLevel !== "app" && topLevel !== "compositions" && topLevel !== "features" && topLevel !== "shared" && topLevel !== "config" && topLevel !== "locales" && !isRootSupportFolder(topLevel)) {
|
|
882
|
+
return report(
|
|
883
|
+
context,
|
|
884
|
+
`Move this source file under an allowed src/ folder; "src/${topLevel}/" is not part of the application structure.`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (topLevel === "features" && segments.length >= 2 && secondLevel !== void 0) {
|
|
888
|
+
if (SUPPORT_FOLDERS.has(secondLevel)) {
|
|
889
|
+
return report(
|
|
890
|
+
context,
|
|
891
|
+
`Move this support folder inside a feature folder; "src/features/${secondLevel}/" does not belong to a feature.`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
if (segments.length === 2) {
|
|
895
|
+
return report(context, "src/features/ must contain only feature folders, not source files.");
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (topLevel === "config") {
|
|
899
|
+
if (segments.length === 2) {
|
|
900
|
+
return report(
|
|
901
|
+
context,
|
|
902
|
+
"A configuration module must be a src/config/<config-name>/ folder with index.ts as its entry point."
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const moduleRoot = configModuleRoot(filename, sourceRoot);
|
|
906
|
+
if (moduleRoot && !fs2.existsSync(path6.join(moduleRoot, "index.ts"))) {
|
|
907
|
+
return report(
|
|
908
|
+
context,
|
|
909
|
+
`Add src/config/${path6.basename(moduleRoot)}/index.ts as the configuration module entry point.`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
if (moduleRoot && segments.length === 3 && path6.basename(filename) !== "index.ts") {
|
|
913
|
+
const kinds2 = exportedKinds(filename);
|
|
914
|
+
const expected2 = expectedSupportFolder(kinds2);
|
|
915
|
+
if (expected2 !== void 0) {
|
|
916
|
+
return report(
|
|
917
|
+
context,
|
|
918
|
+
`Move this configuration support file into src/config/${path6.basename(moduleRoot)}/${expected2}/.`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (topLevel === "app" && isCodeFile(filename)) {
|
|
924
|
+
const basename = path6.basename(filename, path6.extname(filename));
|
|
925
|
+
const currentFolder2 = path6.basename(path6.dirname(filename));
|
|
926
|
+
if (SUPPORT_FOLDERS.has(currentFolder2)) {
|
|
927
|
+
return report(
|
|
928
|
+
context,
|
|
929
|
+
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (!ROUTING_FILES.has(basename) && path6.extname(filename) !== ".css") {
|
|
933
|
+
return report(
|
|
934
|
+
context,
|
|
935
|
+
"src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
if (ROUTING_FILES.has(basename) || path6.extname(filename) === ".css") return {};
|
|
939
|
+
}
|
|
940
|
+
const currentFolder = path6.basename(path6.dirname(filename));
|
|
941
|
+
const kinds = exportedKinds(filename);
|
|
942
|
+
const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path6.basename(filename) === "index.ts";
|
|
943
|
+
if (isConfigModuleRoot) return {};
|
|
944
|
+
if (!SUPPORT_FOLDERS.has(currentFolder)) {
|
|
945
|
+
const expected2 = expectedSupportFolder(kinds);
|
|
946
|
+
if (expected2 !== void 0 && segments.length > 2) {
|
|
947
|
+
return report(
|
|
948
|
+
context,
|
|
949
|
+
`Move this file to a ${expected2}/ folder; ${currentFolder}/ is not a recognized support folder.`
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
const violation2 = componentFolderViolation(segments, sourceRoot);
|
|
953
|
+
if (violation2 !== void 0) return report(context, violation2);
|
|
954
|
+
return {};
|
|
955
|
+
}
|
|
956
|
+
if (kinds.has("component")) {
|
|
957
|
+
return report(
|
|
958
|
+
context,
|
|
959
|
+
`A support folder must not contain a component; move ${path6.basename(filename)} beside ${currentFolder}/.`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
if (mixedSupportKinds(currentFolder, kinds)) {
|
|
963
|
+
return report(
|
|
964
|
+
context,
|
|
965
|
+
`A ${currentFolder}/ folder must not mix unrelated support kinds; keep types, schemas, and constants in their matching folders.`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
const expected = expectedSupportFolder(kinds);
|
|
969
|
+
if (expected !== void 0 && expected !== currentFolder) {
|
|
970
|
+
return report(
|
|
971
|
+
context,
|
|
972
|
+
`Move this file to a ${expected}/ folder; ${currentFolder}/ is reserved for ${expected === "utils" ? "utilities" : expected}.`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const violation = componentFolderViolation(segments, sourceRoot);
|
|
976
|
+
if (violation !== void 0) return report(context, violation);
|
|
977
|
+
return {};
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// eslint/rules/cn-helper.ts
|
|
982
|
+
var HELPER2 = "cn";
|
|
983
|
+
var ENTRY2 = "pasika/cn";
|
|
984
|
+
var DOC2 = "docs/pasika-adoption-guide/rules/cn-helper-rule.md";
|
|
985
|
+
var DECLARED2 = `A file must not declare its own ${HELPER2}; import it from ${ENTRY2}. See ${DOC2}`;
|
|
986
|
+
var IMPORTED2 = `${HELPER2} must be imported from ${ENTRY2}. See ${DOC2}`;
|
|
987
|
+
function isNamed2(node, name) {
|
|
988
|
+
return node?.type === "Identifier" && node.name === name;
|
|
989
|
+
}
|
|
990
|
+
var cnHelperRule = {
|
|
991
|
+
meta: {
|
|
992
|
+
schema: [],
|
|
993
|
+
type: "problem",
|
|
994
|
+
docs: {
|
|
995
|
+
description: `Require ${HELPER2} to be imported from ${ENTRY2} rather than declared in the repository.`
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
create(context) {
|
|
999
|
+
const checkSource = (node) => {
|
|
1000
|
+
if (!node.source || node.source.value === ENTRY2) return;
|
|
1001
|
+
for (const specifier of node.specifiers) {
|
|
1002
|
+
if (!isNamed2(specifier.local, HELPER2)) continue;
|
|
1003
|
+
context.report({ node: specifier, message: IMPORTED2 });
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
return {
|
|
1007
|
+
FunctionDeclaration(node) {
|
|
1008
|
+
if (isNamed2(node.id, HELPER2)) context.report({ node, message: DECLARED2 });
|
|
1009
|
+
},
|
|
1010
|
+
VariableDeclarator(node) {
|
|
1011
|
+
if (isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) {
|
|
1012
|
+
context.report({ node, message: DECLARED2 });
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
ImportDeclaration: checkSource,
|
|
1016
|
+
ExportNamedDeclaration: checkSource
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
999
1021
|
// eslint/rules/component-casing.ts
|
|
1022
|
+
import path7 from "path";
|
|
1000
1023
|
function isPascalCase3(name) {
|
|
1001
1024
|
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
1002
1025
|
}
|