@pyreon/lint 0.12.9 → 0.12.11
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/lib/analysis/cli.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/cli.js +133 -1
- package/lib/cli.js.map +1 -1
- package/lib/index.js +133 -1
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config/presets.ts +4 -0
- package/src/rules/architecture/no-process-dev-gate.ts +183 -0
- package/src/rules/index.ts +4 -1
- package/src/tests/runner.test.ts +273 -4
package/lib/cli.js
CHANGED
|
@@ -658,6 +658,136 @@ const noErrorWithoutPrefix = {
|
|
|
658
658
|
}
|
|
659
659
|
};
|
|
660
660
|
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/rules/architecture/no-process-dev-gate.ts
|
|
663
|
+
/**
|
|
664
|
+
* `pyreon/no-process-dev-gate` — flag the broken `typeof process` dev-mode gate
|
|
665
|
+
* pattern that is dead code in real Vite browser bundles.
|
|
666
|
+
*
|
|
667
|
+
* The pattern this rule catches:
|
|
668
|
+
*
|
|
669
|
+
* ```ts
|
|
670
|
+
* const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
|
|
671
|
+
* ```
|
|
672
|
+
*
|
|
673
|
+
* This works in vitest (Node, `process` is defined) but is **silently dead
|
|
674
|
+
* code in real Vite browser bundles** because Vite does not polyfill
|
|
675
|
+
* `process` for the client. Every dev warning gated on this constant never
|
|
676
|
+
* fires for real users in dev mode.
|
|
677
|
+
*
|
|
678
|
+
* The fix is to use `import.meta.env.DEV`, which Vite/Rolldown literal-replace
|
|
679
|
+
* at build time:
|
|
680
|
+
*
|
|
681
|
+
* ```ts
|
|
682
|
+
* // No const needed — read directly at the use site so the bundler can fold:
|
|
683
|
+
* if (!import.meta.env?.DEV) return
|
|
684
|
+
* ```
|
|
685
|
+
*
|
|
686
|
+
* Vitest sets `import.meta.env.DEV === true` automatically (because it is
|
|
687
|
+
* Vite-based), so existing tests continue to pass.
|
|
688
|
+
*
|
|
689
|
+
* Reference implementation: `packages/fundamentals/flow/src/layout.ts:warnIgnoredOptions`.
|
|
690
|
+
*
|
|
691
|
+
* **Auto-fix**: replaces the assignment with `import.meta.env?.DEV === true`.
|
|
692
|
+
* Does NOT delete the const declaration — that has to happen by hand because
|
|
693
|
+
* the variable name and downstream usages may need updating in callers.
|
|
694
|
+
*
|
|
695
|
+
* **Server-package exception**: server-only files run in Node where `process`
|
|
696
|
+
* is always defined, so the pattern is correct there. The rule skips files
|
|
697
|
+
* matching `packages/zero/`, `packages/core/server/`, `packages/core/runtime-server/`,
|
|
698
|
+
* `packages/tools/vite-plugin/`, and any file containing `/server/` in its
|
|
699
|
+
* path. Add new server packages to `SERVER_PACKAGE_PATTERNS` below.
|
|
700
|
+
*/
|
|
701
|
+
/**
|
|
702
|
+
* File-path patterns for server-only packages. Substring match against the
|
|
703
|
+
* file path passed to the rule. Patterns intentionally do NOT start with `/`
|
|
704
|
+
* so they match both absolute paths (`/Users/.../packages/zero/...`) and
|
|
705
|
+
* relative paths (`packages/zero/...`) — different lint runners pass paths
|
|
706
|
+
* differently.
|
|
707
|
+
*/
|
|
708
|
+
const SERVER_PACKAGE_PATTERNS = [
|
|
709
|
+
"packages/zero/",
|
|
710
|
+
"packages/core/server/",
|
|
711
|
+
"packages/core/runtime-server/",
|
|
712
|
+
"packages/tools/vite-plugin/",
|
|
713
|
+
"packages/tools/cli/",
|
|
714
|
+
"packages/tools/lint/",
|
|
715
|
+
"packages/tools/mcp/",
|
|
716
|
+
"packages/tools/storybook/",
|
|
717
|
+
"packages/tools/typescript/",
|
|
718
|
+
"scripts/"
|
|
719
|
+
];
|
|
720
|
+
function isServerOnlyFile(filePath) {
|
|
721
|
+
return SERVER_PACKAGE_PATTERNS.some((pat) => filePath.includes(pat));
|
|
722
|
+
}
|
|
723
|
+
const noProcessDevGate = {
|
|
724
|
+
meta: {
|
|
725
|
+
id: "pyreon/no-process-dev-gate",
|
|
726
|
+
category: "architecture",
|
|
727
|
+
description: "Forbid `typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\"` as a dev-mode gate. Use `import.meta.env.DEV` instead — `typeof process` is dead code in real Vite browser bundles because Vite does not polyfill `process` for the client.",
|
|
728
|
+
severity: "error",
|
|
729
|
+
fixable: true
|
|
730
|
+
},
|
|
731
|
+
create(context) {
|
|
732
|
+
const filePath = context.getFilePath();
|
|
733
|
+
if (filePath.includes("/tests/") || filePath.includes("/test/") || filePath.includes("/__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.")) return {};
|
|
734
|
+
if (isServerOnlyFile(filePath)) return {};
|
|
735
|
+
/**
|
|
736
|
+
* Match the broken pattern at the AST level. We're looking for any
|
|
737
|
+
* `LogicalExpression` whose two sides are:
|
|
738
|
+
*
|
|
739
|
+
* 1. `typeof process !== 'undefined'` (a UnaryExpression on the LHS
|
|
740
|
+
* of a BinaryExpression with operator `!==`)
|
|
741
|
+
* 2. `process.env.NODE_ENV !== 'production'` (a MemberExpression on
|
|
742
|
+
* the LHS of a BinaryExpression with operator `!==`)
|
|
743
|
+
*
|
|
744
|
+
* The order can be either way (process check first or NODE_ENV check
|
|
745
|
+
* first), and the operator can be `&&` or `||` (we only flag `&&`
|
|
746
|
+
* because `||` doesn't make sense as a dev gate).
|
|
747
|
+
*/
|
|
748
|
+
function isTypeofProcessCheck(node) {
|
|
749
|
+
if (node?.type !== "BinaryExpression") return false;
|
|
750
|
+
if (node.operator !== "!==" && node.operator !== "!=") return false;
|
|
751
|
+
const left = node.left;
|
|
752
|
+
const right = node.right;
|
|
753
|
+
if (left?.type !== "UnaryExpression" || left.operator !== "typeof") return false;
|
|
754
|
+
if (left.argument?.type !== "Identifier" || left.argument.name !== "process") return false;
|
|
755
|
+
if ((right?.type === "Literal" || right?.type === "StringLiteral") && right.value === "undefined") return true;
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
function isNodeEnvCheck(node) {
|
|
759
|
+
if (node?.type !== "BinaryExpression") return false;
|
|
760
|
+
if (node.operator !== "!==" && node.operator !== "!=") return false;
|
|
761
|
+
const left = node.left;
|
|
762
|
+
const right = node.right;
|
|
763
|
+
if (left?.type !== "MemberExpression") return false;
|
|
764
|
+
if (left.object?.type !== "MemberExpression") return false;
|
|
765
|
+
if (left.object.object?.type !== "Identifier" || left.object.object.name !== "process") return false;
|
|
766
|
+
if (left.object.property?.type !== "Identifier" || left.object.property.name !== "env") return false;
|
|
767
|
+
if (left.property?.type !== "Identifier" || left.property.name !== "NODE_ENV") return false;
|
|
768
|
+
if ((right?.type === "Literal" || right?.type === "StringLiteral") && right.value === "production") return true;
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
function isBrokenDevGate(node) {
|
|
772
|
+
if (node?.type !== "LogicalExpression") return false;
|
|
773
|
+
if (node.operator !== "&&") return false;
|
|
774
|
+
return isTypeofProcessCheck(node.left) && isNodeEnvCheck(node.right) || isNodeEnvCheck(node.left) && isTypeofProcessCheck(node.right);
|
|
775
|
+
}
|
|
776
|
+
return { LogicalExpression(node) {
|
|
777
|
+
if (!isBrokenDevGate(node)) return;
|
|
778
|
+
const span = getSpan(node);
|
|
779
|
+
context.report({
|
|
780
|
+
message: "`typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\"` is dead code in real Vite browser bundles — Vite does not polyfill `process`, so this guard is `false` and any wrapped dev warnings never fire for real users. Use `import.meta.env.DEV` instead, which Vite literal-replaces at build time and tree-shakes correctly in prod. Reference implementation: `packages/fundamentals/flow/src/layout.ts:warnIgnoredOptions`.",
|
|
781
|
+
span,
|
|
782
|
+
fix: {
|
|
783
|
+
span,
|
|
784
|
+
replacement: "import.meta.env?.DEV === true"
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
} };
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
661
791
|
//#endregion
|
|
662
792
|
//#region src/rules/form/no-submit-without-validation.ts
|
|
663
793
|
const noSubmitWithoutValidation = {
|
|
@@ -2529,6 +2659,7 @@ const allRules = [
|
|
|
2529
2659
|
noCrossLayerImport,
|
|
2530
2660
|
devGuardWarnings,
|
|
2531
2661
|
noErrorWithoutPrefix,
|
|
2662
|
+
noProcessDevGate,
|
|
2532
2663
|
noStoreOutsideProvider,
|
|
2533
2664
|
noMutateStoreState,
|
|
2534
2665
|
noDuplicateStoreId,
|
|
@@ -2583,7 +2714,8 @@ function buildLib() {
|
|
|
2583
2714
|
"pyreon/no-circular-import": "error",
|
|
2584
2715
|
"pyreon/no-cross-layer-import": "error",
|
|
2585
2716
|
"pyreon/dev-guard-warnings": "error",
|
|
2586
|
-
"pyreon/no-error-without-prefix": "error"
|
|
2717
|
+
"pyreon/no-error-without-prefix": "error",
|
|
2718
|
+
"pyreon/no-process-dev-gate": "error"
|
|
2587
2719
|
} };
|
|
2588
2720
|
}
|
|
2589
2721
|
const presetBuilders = {
|