@sarj/eslint-plugin 15.11.0 → 15.12.0
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/index.cjs +1662 -1305
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +379 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2009,6 +2009,67 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
|
|
|
2009
2009
|
}
|
|
2010
2010
|
});
|
|
2011
2011
|
|
|
2012
|
+
// src/rules/no-dangerously-allow-svg.ts
|
|
2013
|
+
import "@typescript-eslint/utils";
|
|
2014
|
+
var NEXT_CONFIG_RE = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
|
|
2015
|
+
var noDangerouslyAllowSvgDocumentation = {
|
|
2016
|
+
summary: "Next.js image configuration enables unsanitized SVG rendering",
|
|
2017
|
+
rationale: "SVG files can contain scripts and other active content; enabling dangerouslyAllowSVG makes the image optimizer serve that content from the application origin.",
|
|
2018
|
+
remediation: "Keep dangerouslyAllowSVG disabled. If SVG delivery is unavoidable, use a separately reviewed asset path with restrictive Content-Disposition and Content-Security-Policy headers.",
|
|
2019
|
+
category: "security",
|
|
2020
|
+
limitations: [
|
|
2021
|
+
"Only a literal true assigned to dangerouslyAllowSVG in a next.config source file is reported; computed or imported configuration is intentionally not inferred."
|
|
2022
|
+
],
|
|
2023
|
+
examples: [
|
|
2024
|
+
{
|
|
2025
|
+
id: "svg-disabled",
|
|
2026
|
+
title: "Keep active SVG delivery disabled",
|
|
2027
|
+
outcome: "no-match",
|
|
2028
|
+
files: [{ path: "next.config.mjs", source: "export default { images: { dangerouslyAllowSVG: false } };\n" }],
|
|
2029
|
+
focusPath: "next.config.mjs",
|
|
2030
|
+
expectedCount: 0,
|
|
2031
|
+
public: true
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
id: "svg-enabled",
|
|
2035
|
+
title: "Do not enable active SVG delivery",
|
|
2036
|
+
outcome: "match",
|
|
2037
|
+
files: [{ path: "next.config.mjs", source: "export default { images: { dangerouslyAllowSVG: true } };\n" }],
|
|
2038
|
+
focusPath: "next.config.mjs",
|
|
2039
|
+
expectedCount: 1,
|
|
2040
|
+
public: true
|
|
2041
|
+
}
|
|
2042
|
+
]
|
|
2043
|
+
};
|
|
2044
|
+
function propertyName(node) {
|
|
2045
|
+
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
2046
|
+
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
2047
|
+
return null;
|
|
2048
|
+
}
|
|
2049
|
+
var no_dangerously_allow_svg_default = createRule({
|
|
2050
|
+
name: "no-dangerously-allow-svg",
|
|
2051
|
+
documentation: noDangerouslyAllowSvgDocumentation,
|
|
2052
|
+
meta: {
|
|
2053
|
+
type: "problem",
|
|
2054
|
+
docs: { description: noDangerouslyAllowSvgDocumentation.summary },
|
|
2055
|
+
schema: [],
|
|
2056
|
+
messages: {
|
|
2057
|
+
noDangerouslyAllowSvg: "Do not enable dangerouslyAllowSVG. SVG can carry active content served from the application origin."
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
defaultOptions: [],
|
|
2061
|
+
create(context) {
|
|
2062
|
+
if (!NEXT_CONFIG_RE.test(context.filename.replaceAll("\\", "/"))) return {};
|
|
2063
|
+
return {
|
|
2064
|
+
Property(node) {
|
|
2065
|
+
if (propertyName(node) === "dangerouslyAllowSVG" && node.value.type === "Literal" && node.value.value === true) {
|
|
2066
|
+
context.report({ node, messageId: "noDangerouslyAllowSvg" });
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2012
2073
|
// src/rules/no-dynamic-sql.ts
|
|
2013
2074
|
import { AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
|
|
2014
2075
|
|
|
@@ -3004,7 +3065,7 @@ var no_hand_rolled_sleep_default = createRule({
|
|
|
3004
3065
|
return {};
|
|
3005
3066
|
}
|
|
3006
3067
|
const checkClientModules = optionsArg?.checkClientModules ?? false;
|
|
3007
|
-
function
|
|
3068
|
+
function isClientModule2() {
|
|
3008
3069
|
if (/\.[cm]?[jt]sx$/.test(filename)) {
|
|
3009
3070
|
return true;
|
|
3010
3071
|
}
|
|
@@ -3024,7 +3085,7 @@ var no_hand_rolled_sleep_default = createRule({
|
|
|
3024
3085
|
if (checkClientModules) {
|
|
3025
3086
|
return true;
|
|
3026
3087
|
}
|
|
3027
|
-
clientModule ??=
|
|
3088
|
+
clientModule ??= isClientModule2();
|
|
3028
3089
|
return !clientModule;
|
|
3029
3090
|
};
|
|
3030
3091
|
return {
|
|
@@ -5175,6 +5236,67 @@ var no_positional_tuple_return_default = createRule({
|
|
|
5175
5236
|
}
|
|
5176
5237
|
});
|
|
5177
5238
|
|
|
5239
|
+
// src/rules/no-production-browser-source-maps.ts
|
|
5240
|
+
import "@typescript-eslint/utils";
|
|
5241
|
+
var NEXT_CONFIG_RE2 = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
|
|
5242
|
+
var noProductionBrowserSourceMapsDocumentation = {
|
|
5243
|
+
summary: "Next.js production browser source maps expose application source",
|
|
5244
|
+
rationale: "Next.js production browser source maps publish original client source and implementation details to every browser that can load the deployment.",
|
|
5245
|
+
remediation: "Leave productionBrowserSourceMaps disabled and upload private source maps directly to the error-monitoring service during the build.",
|
|
5246
|
+
category: "security",
|
|
5247
|
+
limitations: [
|
|
5248
|
+
"Only a literal true assigned in a next.config source file is reported; computed or imported configuration is intentionally not inferred."
|
|
5249
|
+
],
|
|
5250
|
+
examples: [
|
|
5251
|
+
{
|
|
5252
|
+
id: "private-source-maps",
|
|
5253
|
+
title: "Keep browser source maps private",
|
|
5254
|
+
outcome: "no-match",
|
|
5255
|
+
files: [{ path: "next.config.mjs", source: "export default { productionBrowserSourceMaps: false };\n" }],
|
|
5256
|
+
focusPath: "next.config.mjs",
|
|
5257
|
+
expectedCount: 0,
|
|
5258
|
+
public: true
|
|
5259
|
+
},
|
|
5260
|
+
{
|
|
5261
|
+
id: "public-source-maps",
|
|
5262
|
+
title: "Do not publish production browser source maps",
|
|
5263
|
+
outcome: "match",
|
|
5264
|
+
files: [{ path: "next.config.mjs", source: "export default { productionBrowserSourceMaps: true };\n" }],
|
|
5265
|
+
focusPath: "next.config.mjs",
|
|
5266
|
+
expectedCount: 1,
|
|
5267
|
+
public: true
|
|
5268
|
+
}
|
|
5269
|
+
]
|
|
5270
|
+
};
|
|
5271
|
+
function propertyName2(node) {
|
|
5272
|
+
if (!node.computed && node.key.type === "Identifier") return node.key.name;
|
|
5273
|
+
if (node.key.type === "Literal" && typeof node.key.value === "string") return node.key.value;
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
var no_production_browser_source_maps_default = createRule({
|
|
5277
|
+
name: "no-production-browser-source-maps",
|
|
5278
|
+
documentation: noProductionBrowserSourceMapsDocumentation,
|
|
5279
|
+
meta: {
|
|
5280
|
+
type: "problem",
|
|
5281
|
+
docs: { description: noProductionBrowserSourceMapsDocumentation.summary },
|
|
5282
|
+
schema: [],
|
|
5283
|
+
messages: {
|
|
5284
|
+
noProductionBrowserSourceMaps: "Do not publish production browser source maps. Upload private maps to your monitoring service instead."
|
|
5285
|
+
}
|
|
5286
|
+
},
|
|
5287
|
+
defaultOptions: [],
|
|
5288
|
+
create(context) {
|
|
5289
|
+
if (!NEXT_CONFIG_RE2.test(context.filename.replaceAll("\\", "/"))) return {};
|
|
5290
|
+
return {
|
|
5291
|
+
Property(node) {
|
|
5292
|
+
if (propertyName2(node) === "productionBrowserSourceMaps" && node.value.type === "Literal" && node.value.value === true) {
|
|
5293
|
+
context.report({ node, messageId: "noProductionBrowserSourceMaps" });
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
});
|
|
5299
|
+
|
|
5178
5300
|
// src/rules/no-raw-env.ts
|
|
5179
5301
|
import "@typescript-eslint/utils";
|
|
5180
5302
|
var noRawEnvDocumentation = {
|
|
@@ -6568,6 +6690,85 @@ var no_secret_in_log_default = createRule({
|
|
|
6568
6690
|
}
|
|
6569
6691
|
});
|
|
6570
6692
|
|
|
6693
|
+
// src/rules/no-server-env-in-client-component.ts
|
|
6694
|
+
import "@typescript-eslint/utils";
|
|
6695
|
+
var SERVER_ENV_MODULE_RE = /(?:^|\/)(?:server-env|server-settings)(?:\.[cm]?[jt]sx?)?$/;
|
|
6696
|
+
var noServerEnvInClientComponentDocumentation = {
|
|
6697
|
+
summary: "server-only environment settings imported by a client component",
|
|
6698
|
+
rationale: "Next.js client modules run in the browser, where server-only environment values are unavailable; importing a server settings module can produce undefined configuration or bundle a secret-bearing module into the client graph.",
|
|
6699
|
+
remediation: "Pass an explicitly public value from a Server Component, or import it from a separately validated client-settings module backed only by NEXT_PUBLIC_* values.",
|
|
6700
|
+
category: "correctness",
|
|
6701
|
+
limitations: [
|
|
6702
|
+
"Only static value imports in files with a top-level 'use client' directive and conventionally named server-env/server-settings modules are checked."
|
|
6703
|
+
],
|
|
6704
|
+
examples: [
|
|
6705
|
+
{
|
|
6706
|
+
id: "public-client-settings",
|
|
6707
|
+
title: "Import a browser-safe settings boundary",
|
|
6708
|
+
outcome: "no-match",
|
|
6709
|
+
files: [
|
|
6710
|
+
{
|
|
6711
|
+
path: "status-card.tsx",
|
|
6712
|
+
source: "'use client';\nimport { CLIENT_SETTINGS } from '@/client-settings';\nexport const StatusCard = () => <p>{CLIENT_SETTINGS.apiOrigin}</p>;\n"
|
|
6713
|
+
}
|
|
6714
|
+
],
|
|
6715
|
+
focusPath: "status-card.tsx",
|
|
6716
|
+
expectedCount: 0,
|
|
6717
|
+
public: true
|
|
6718
|
+
},
|
|
6719
|
+
{
|
|
6720
|
+
id: "server-settings-client-import",
|
|
6721
|
+
title: "Do not pull server settings into a client bundle",
|
|
6722
|
+
outcome: "match",
|
|
6723
|
+
files: [
|
|
6724
|
+
{
|
|
6725
|
+
path: "status-card.tsx",
|
|
6726
|
+
source: "'use client';\nimport { SERVER_SETTINGS } from '@/server-settings';\nexport const StatusCard = () => <p>{SERVER_SETTINGS.apiOrigin}</p>;\n"
|
|
6727
|
+
}
|
|
6728
|
+
],
|
|
6729
|
+
focusPath: "status-card.tsx",
|
|
6730
|
+
expectedCount: 1,
|
|
6731
|
+
public: true
|
|
6732
|
+
}
|
|
6733
|
+
]
|
|
6734
|
+
};
|
|
6735
|
+
function isClientModule(program) {
|
|
6736
|
+
return program.body.some(
|
|
6737
|
+
(statement) => statement.type === "ExpressionStatement" && statement.directive === "use client"
|
|
6738
|
+
);
|
|
6739
|
+
}
|
|
6740
|
+
function isTypeOnlyImport(node) {
|
|
6741
|
+
return node.importKind === "type" || node.specifiers.length > 0 && node.specifiers.every(
|
|
6742
|
+
(specifier) => specifier.type === "ImportSpecifier" && specifier.importKind === "type"
|
|
6743
|
+
);
|
|
6744
|
+
}
|
|
6745
|
+
var no_server_env_in_client_component_default = createRule({
|
|
6746
|
+
name: "no-server-env-in-client-component",
|
|
6747
|
+
documentation: noServerEnvInClientComponentDocumentation,
|
|
6748
|
+
meta: {
|
|
6749
|
+
type: "problem",
|
|
6750
|
+
docs: { description: noServerEnvInClientComponentDocumentation.summary },
|
|
6751
|
+
schema: [],
|
|
6752
|
+
messages: {
|
|
6753
|
+
noServerEnvInClientComponent: "A 'use client' module cannot import server-only settings. Pass public data from a Server Component or use a validated client-settings module."
|
|
6754
|
+
}
|
|
6755
|
+
},
|
|
6756
|
+
defaultOptions: [],
|
|
6757
|
+
create(context) {
|
|
6758
|
+
let clientModule = false;
|
|
6759
|
+
return {
|
|
6760
|
+
Program(node) {
|
|
6761
|
+
clientModule = isClientModule(node);
|
|
6762
|
+
},
|
|
6763
|
+
ImportDeclaration(node) {
|
|
6764
|
+
if (clientModule && !isTypeOnlyImport(node) && typeof node.source.value === "string" && SERVER_ENV_MODULE_RE.test(node.source.value)) {
|
|
6765
|
+
context.report({ node, messageId: "noServerEnvInClientComponent" });
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
};
|
|
6769
|
+
}
|
|
6770
|
+
});
|
|
6771
|
+
|
|
6571
6772
|
// src/rules/no-select-star.ts
|
|
6572
6773
|
import "@typescript-eslint/utils";
|
|
6573
6774
|
var noSelectStarDocumentation = {
|
|
@@ -11250,7 +11451,7 @@ var preferNonNullableCollectionDocumentation = {
|
|
|
11250
11451
|
]
|
|
11251
11452
|
};
|
|
11252
11453
|
var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
11253
|
-
function
|
|
11454
|
+
function propertyName3(node) {
|
|
11254
11455
|
const key = node.key;
|
|
11255
11456
|
if (node.computed) return null;
|
|
11256
11457
|
if (key.type === AST_NODE_TYPES48.Identifier) return key.name;
|
|
@@ -11263,7 +11464,7 @@ function isArrayType(node) {
|
|
|
11263
11464
|
}
|
|
11264
11465
|
function nullableProperty(node) {
|
|
11265
11466
|
if (node.optional) return null;
|
|
11266
|
-
const name =
|
|
11467
|
+
const name = propertyName3(node);
|
|
11267
11468
|
const annotation = node.typeAnnotation?.typeAnnotation;
|
|
11268
11469
|
if (name === null || annotation?.type !== AST_NODE_TYPES48.TSUnionType) return null;
|
|
11269
11470
|
const concrete = annotation.types.filter(
|
|
@@ -11473,7 +11674,7 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
11473
11674
|
context.report({
|
|
11474
11675
|
node,
|
|
11475
11676
|
messageId: "preferNonNullableCollection",
|
|
11476
|
-
data: { name:
|
|
11677
|
+
data: { name: propertyName3(node) ?? "collection" }
|
|
11477
11678
|
});
|
|
11478
11679
|
}
|
|
11479
11680
|
}
|
|
@@ -13653,11 +13854,11 @@ var prefer_zod_infer_default = createRule({
|
|
|
13653
13854
|
continue;
|
|
13654
13855
|
}
|
|
13655
13856
|
const key = member.key;
|
|
13656
|
-
const
|
|
13657
|
-
if (
|
|
13857
|
+
const propertyName5 = key.type === AST_NODE_TYPES54.Identifier ? key.name : key.type === AST_NODE_TYPES54.Literal && typeof key.value === "string" ? key.value : null;
|
|
13858
|
+
if (propertyName5 === null) {
|
|
13658
13859
|
continue;
|
|
13659
13860
|
}
|
|
13660
|
-
const propertyTokens = nameTokens(
|
|
13861
|
+
const propertyTokens = nameTokens(propertyName5);
|
|
13661
13862
|
if (propertyTokens.length < 2) {
|
|
13662
13863
|
continue;
|
|
13663
13864
|
}
|
|
@@ -13672,7 +13873,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
13672
13873
|
node: annotation,
|
|
13673
13874
|
owner,
|
|
13674
13875
|
ownerName,
|
|
13675
|
-
propertyName:
|
|
13876
|
+
propertyName: propertyName5,
|
|
13676
13877
|
propertyTokens
|
|
13677
13878
|
});
|
|
13678
13879
|
}
|
|
@@ -14787,7 +14988,7 @@ function isStaticValue(node) {
|
|
|
14787
14988
|
}
|
|
14788
14989
|
return false;
|
|
14789
14990
|
}
|
|
14790
|
-
function
|
|
14991
|
+
function propertyName4(property) {
|
|
14791
14992
|
if (property.computed) return null;
|
|
14792
14993
|
if (property.key.type === AST_NODE_TYPES58.Identifier) return property.key.name;
|
|
14793
14994
|
return typeof property.key.value === "string" ? property.key.value : null;
|
|
@@ -14824,7 +15025,7 @@ var require_static_next_matcher_default = createRule({
|
|
|
14824
15025
|
continue;
|
|
14825
15026
|
}
|
|
14826
15027
|
for (const property of config.properties) {
|
|
14827
|
-
if (property.type !== AST_NODE_TYPES58.Property ||
|
|
15028
|
+
if (property.type !== AST_NODE_TYPES58.Property || propertyName4(property) !== "matcher" || property.value.type === AST_NODE_TYPES58.AssignmentPattern) {
|
|
14828
15029
|
continue;
|
|
14829
15030
|
}
|
|
14830
15031
|
if (!isStaticValue(property.value)) {
|
|
@@ -14837,10 +15038,146 @@ var require_static_next_matcher_default = createRule({
|
|
|
14837
15038
|
}
|
|
14838
15039
|
});
|
|
14839
15040
|
|
|
15041
|
+
// src/rules/require-use-form-default-values.ts
|
|
15042
|
+
import { ASTUtils as ASTUtils19 } from "@typescript-eslint/utils";
|
|
15043
|
+
var requireUseFormDefaultValuesDocumentation = {
|
|
15044
|
+
summary: "react-hook-form useForm call without defaultValues",
|
|
15045
|
+
rationale: "Without an explicit initial value, fields can change from uncontrolled to controlled as data arrives, reset behavior becomes ambiguous, and the form's initial shape no longer documents the values users can edit.",
|
|
15046
|
+
remediation: "Pass an object with a defaultValues property to useForm; use empty strings, nulls, or schema-appropriate values deliberately for every controlled field.",
|
|
15047
|
+
category: "correctness",
|
|
15048
|
+
limitations: [
|
|
15049
|
+
"Only direct calls to a scope-resolved useForm value imported from react-hook-form are checked; wrapper hooks and computed option objects are intentionally not inferred."
|
|
15050
|
+
],
|
|
15051
|
+
examples: [
|
|
15052
|
+
{
|
|
15053
|
+
id: "form-with-initial-values",
|
|
15054
|
+
title: "Give the form an explicit initial shape",
|
|
15055
|
+
outcome: "no-match",
|
|
15056
|
+
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form';\nconst form = useForm({ defaultValues: { name: '' } });\n" }],
|
|
15057
|
+
focusPath: "profile-form.tsx",
|
|
15058
|
+
expectedCount: 0,
|
|
15059
|
+
public: true
|
|
15060
|
+
},
|
|
15061
|
+
{
|
|
15062
|
+
id: "form-without-initial-values",
|
|
15063
|
+
title: "Do not leave form initialization implicit",
|
|
15064
|
+
outcome: "match",
|
|
15065
|
+
files: [{ path: "profile-form.tsx", source: "import { useForm } from 'react-hook-form';\nconst form = useForm({ mode: 'onChange' });\n" }],
|
|
15066
|
+
focusPath: "profile-form.tsx",
|
|
15067
|
+
expectedCount: 1,
|
|
15068
|
+
public: true
|
|
15069
|
+
}
|
|
15070
|
+
]
|
|
15071
|
+
};
|
|
15072
|
+
function hasDefaultValues(options) {
|
|
15073
|
+
return options?.type === "ObjectExpression" && options.properties.some(
|
|
15074
|
+
(property) => property.type === "Property" && !property.computed && (property.key.type === "Identifier" && property.key.name === "defaultValues" || property.key.type === "Literal" && property.key.value === "defaultValues")
|
|
15075
|
+
);
|
|
15076
|
+
}
|
|
15077
|
+
var require_use_form_default_values_default = createRule({
|
|
15078
|
+
name: "require-use-form-default-values",
|
|
15079
|
+
documentation: requireUseFormDefaultValuesDocumentation,
|
|
15080
|
+
meta: {
|
|
15081
|
+
type: "problem",
|
|
15082
|
+
docs: { description: requireUseFormDefaultValuesDocumentation.summary },
|
|
15083
|
+
schema: [],
|
|
15084
|
+
messages: {
|
|
15085
|
+
requireUseFormDefaultValues: "Pass explicit defaultValues to useForm so fields have a stable initial shape and reset behavior."
|
|
15086
|
+
}
|
|
15087
|
+
},
|
|
15088
|
+
defaultOptions: [],
|
|
15089
|
+
create(context) {
|
|
15090
|
+
const importedHooks = /* @__PURE__ */ new Set();
|
|
15091
|
+
return {
|
|
15092
|
+
ImportDeclaration(node) {
|
|
15093
|
+
if (node.source.value !== "react-hook-form") return;
|
|
15094
|
+
for (const specifier of node.specifiers) {
|
|
15095
|
+
if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
|
|
15096
|
+
const variable = ASTUtils19.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
|
|
15097
|
+
if (variable) importedHooks.add(variable);
|
|
15098
|
+
}
|
|
15099
|
+
},
|
|
15100
|
+
CallExpression(node) {
|
|
15101
|
+
if (node.callee.type !== "Identifier") return;
|
|
15102
|
+
const variable = ASTUtils19.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
|
|
15103
|
+
const options = node.arguments[0];
|
|
15104
|
+
if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasDefaultValues(options)) return;
|
|
15105
|
+
context.report({ node, messageId: "requireUseFormDefaultValues" });
|
|
15106
|
+
}
|
|
15107
|
+
};
|
|
15108
|
+
}
|
|
15109
|
+
});
|
|
15110
|
+
|
|
15111
|
+
// src/rules/require-use-server-in-actions-file.ts
|
|
15112
|
+
import "@typescript-eslint/utils";
|
|
15113
|
+
var ACTION_MODULE_RE = /(?:^|\/)app\/.*\/(?:actions|[^/]+-actions)\.[cm]?[jt]s$/u;
|
|
15114
|
+
var requireUseServerInActionsFileDocumentation = {
|
|
15115
|
+
summary: "route action module missing the use server directive",
|
|
15116
|
+
rationale: "An exported async function is not callable as a Server Action merely because its file is named actions.ts. Without the module directive, a client import can fail or pull server-only implementation details across the client boundary.",
|
|
15117
|
+
remediation: "Put 'use server' at the start of the route action module.",
|
|
15118
|
+
category: "correctness",
|
|
15119
|
+
limitations: [
|
|
15120
|
+
"Only exported async functions in actions.ts or *-actions.ts below an app directory are checked; other naming schemes and inline Server Actions are intentionally outside the rule."
|
|
15121
|
+
],
|
|
15122
|
+
examples: [
|
|
15123
|
+
{
|
|
15124
|
+
id: "server-action-module",
|
|
15125
|
+
title: "Mark the action module as server-only",
|
|
15126
|
+
outcome: "no-match",
|
|
15127
|
+
files: [{ path: "app/orders/actions.ts", source: "'use server';\nexport async function cancelOrder() {}\n" }],
|
|
15128
|
+
focusPath: "app/orders/actions.ts",
|
|
15129
|
+
expectedCount: 0,
|
|
15130
|
+
public: true
|
|
15131
|
+
},
|
|
15132
|
+
{
|
|
15133
|
+
id: "unmarked-action-module",
|
|
15134
|
+
title: "Do not rely on the filename to create a Server Action",
|
|
15135
|
+
outcome: "match",
|
|
15136
|
+
files: [{ path: "app/orders/actions.ts", source: "export async function cancelOrder() {}\n" }],
|
|
15137
|
+
focusPath: "app/orders/actions.ts",
|
|
15138
|
+
expectedCount: 1,
|
|
15139
|
+
public: true
|
|
15140
|
+
}
|
|
15141
|
+
]
|
|
15142
|
+
};
|
|
15143
|
+
function isExportedAsyncFunction(node) {
|
|
15144
|
+
const declaration = node.declaration;
|
|
15145
|
+
if (declaration?.type === "FunctionDeclaration") return declaration.async;
|
|
15146
|
+
return declaration?.type === "VariableDeclaration" && declaration.declarations.some(
|
|
15147
|
+
(item) => item.init?.type === "ArrowFunctionExpression" || item.init?.type === "FunctionExpression" ? item.init.async : false
|
|
15148
|
+
);
|
|
15149
|
+
}
|
|
15150
|
+
var require_use_server_in_actions_file_default = createRule({
|
|
15151
|
+
name: "require-use-server-in-actions-file",
|
|
15152
|
+
documentation: requireUseServerInActionsFileDocumentation,
|
|
15153
|
+
meta: {
|
|
15154
|
+
type: "problem",
|
|
15155
|
+
docs: { description: requireUseServerInActionsFileDocumentation.summary },
|
|
15156
|
+
schema: [],
|
|
15157
|
+
messages: {
|
|
15158
|
+
requireUseServerInActionsFile: "This route action module exports an async function but is missing a leading 'use server' directive."
|
|
15159
|
+
}
|
|
15160
|
+
},
|
|
15161
|
+
defaultOptions: [],
|
|
15162
|
+
create(context) {
|
|
15163
|
+
return {
|
|
15164
|
+
Program(node) {
|
|
15165
|
+
const filename = context.filename.replaceAll("\\", "/");
|
|
15166
|
+
if (!ACTION_MODULE_RE.test(filename)) return;
|
|
15167
|
+
if (node.body.some((statement) => statement.type === "ExpressionStatement" && statement.directive === "use server")) return;
|
|
15168
|
+
const exportedAction = node.body.find(
|
|
15169
|
+
(statement) => statement.type === "ExportNamedDeclaration" && isExportedAsyncFunction(statement)
|
|
15170
|
+
);
|
|
15171
|
+
if (exportedAction) context.report({ node: exportedAction, messageId: "requireUseServerInActionsFile" });
|
|
15172
|
+
}
|
|
15173
|
+
};
|
|
15174
|
+
}
|
|
15175
|
+
});
|
|
15176
|
+
|
|
14840
15177
|
// src/rules/require-zod-form-validation.ts
|
|
14841
15178
|
import {
|
|
14842
15179
|
AST_NODE_TYPES as AST_NODE_TYPES59,
|
|
14843
|
-
ASTUtils as
|
|
15180
|
+
ASTUtils as ASTUtils20
|
|
14844
15181
|
} from "@typescript-eslint/utils";
|
|
14845
15182
|
var requireZodFormValidationDocumentation = {
|
|
14846
15183
|
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
@@ -14908,7 +15245,7 @@ var require_zod_form_validation_default = createRule({
|
|
|
14908
15245
|
return {};
|
|
14909
15246
|
}
|
|
14910
15247
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
14911
|
-
const resolvedBinding = (identifier) =>
|
|
15248
|
+
const resolvedBinding = (identifier) => ASTUtils20.findVariable(
|
|
14912
15249
|
context.sourceCode.getScope(identifier),
|
|
14913
15250
|
identifier.name
|
|
14914
15251
|
);
|
|
@@ -15205,7 +15542,7 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
15205
15542
|
});
|
|
15206
15543
|
|
|
15207
15544
|
// src/rules/stepdown.ts
|
|
15208
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as
|
|
15545
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils21 } from "@typescript-eslint/utils";
|
|
15209
15546
|
var stepdownDocumentation = {
|
|
15210
15547
|
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
15211
15548
|
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
@@ -15405,7 +15742,7 @@ function methodName(node) {
|
|
|
15405
15742
|
return !node.computed && node.key.type === AST_NODE_TYPES60.Identifier ? node.key.name : null;
|
|
15406
15743
|
}
|
|
15407
15744
|
function referencedMethod(context, node, classVariables) {
|
|
15408
|
-
const objectVariable = node.object.type === AST_NODE_TYPES60.Identifier ?
|
|
15745
|
+
const objectVariable = node.object.type === AST_NODE_TYPES60.Identifier ? ASTUtils21.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
|
|
15409
15746
|
const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
|
|
15410
15747
|
if (node.object.type !== AST_NODE_TYPES60.ThisExpression && !isClassReference) return null;
|
|
15411
15748
|
if (node.property.type === AST_NODE_TYPES60.PrivateIdentifier) return `#${node.property.name}`;
|
|
@@ -15458,11 +15795,11 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
15458
15795
|
const pinned = /* @__PURE__ */ new Set();
|
|
15459
15796
|
const classVariables = /* @__PURE__ */ new Set();
|
|
15460
15797
|
if (node.id !== null) {
|
|
15461
|
-
const internal =
|
|
15798
|
+
const internal = ASTUtils21.findVariable(context.sourceCode.getScope(node), node.id.name);
|
|
15462
15799
|
if (internal !== null) classVariables.add(internal);
|
|
15463
15800
|
}
|
|
15464
15801
|
if (node.type === AST_NODE_TYPES60.ClassExpression && node.parent.type === AST_NODE_TYPES60.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES60.Identifier) {
|
|
15465
|
-
const outer =
|
|
15802
|
+
const outer = ASTUtils21.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
|
|
15466
15803
|
if (outer !== null) classVariables.add(outer);
|
|
15467
15804
|
}
|
|
15468
15805
|
for (const method of methods) {
|
|
@@ -15498,7 +15835,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
15498
15835
|
return;
|
|
15499
15836
|
}
|
|
15500
15837
|
if (binding.type !== AST_NODE_TYPES60.Identifier) return;
|
|
15501
|
-
const variable =
|
|
15838
|
+
const variable = ASTUtils21.findVariable(context.sourceCode.getScope(binding), binding.name);
|
|
15502
15839
|
if (variable !== null) {
|
|
15503
15840
|
methodClassVariables.add(variable);
|
|
15504
15841
|
methodAliases.add(variable);
|
|
@@ -15528,7 +15865,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
15528
15865
|
return;
|
|
15529
15866
|
}
|
|
15530
15867
|
if (!privateNames.has(target)) return;
|
|
15531
|
-
const objectVariable = current.object.type === AST_NODE_TYPES60.Identifier ?
|
|
15868
|
+
const objectVariable = current.object.type === AST_NODE_TYPES60.Identifier ? ASTUtils21.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
|
|
15532
15869
|
if (objectVariable !== null && methodAliases.has(objectVariable)) {
|
|
15533
15870
|
pinned.add(target);
|
|
15534
15871
|
return;
|
|
@@ -15976,7 +16313,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
|
|
|
15976
16313
|
// src/rules/zod-naming-convention.ts
|
|
15977
16314
|
import {
|
|
15978
16315
|
AST_NODE_TYPES as AST_NODE_TYPES62,
|
|
15979
|
-
ASTUtils as
|
|
16316
|
+
ASTUtils as ASTUtils22
|
|
15980
16317
|
} from "@typescript-eslint/utils";
|
|
15981
16318
|
var zodNamingConventionDocumentation = {
|
|
15982
16319
|
summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
|
|
@@ -16069,7 +16406,7 @@ var zod_naming_convention_default = createRule({
|
|
|
16069
16406
|
const acceptsSchemaWord = convention !== "prefix";
|
|
16070
16407
|
const zodBindings = /* @__PURE__ */ new Set();
|
|
16071
16408
|
function resolvedBinding(identifier) {
|
|
16072
|
-
return
|
|
16409
|
+
return ASTUtils22.findVariable(
|
|
16073
16410
|
context.sourceCode.getScope(identifier),
|
|
16074
16411
|
identifier.name
|
|
16075
16412
|
);
|
|
@@ -16206,6 +16543,7 @@ var rules = {
|
|
|
16206
16543
|
"no-comment-cruft": no_comment_cruft_default,
|
|
16207
16544
|
"no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
|
|
16208
16545
|
"no-duplicate-lifecycle-refresh-listeners": no_duplicate_lifecycle_refresh_listeners_default,
|
|
16546
|
+
"no-dangerously-allow-svg": no_dangerously_allow_svg_default,
|
|
16209
16547
|
"no-dynamic-sql": no_dynamic_sql_default,
|
|
16210
16548
|
"no-enum": no_enum_default,
|
|
16211
16549
|
"no-fat-try-blocks": no_fat_try_blocks_default,
|
|
@@ -16221,6 +16559,7 @@ var rules = {
|
|
|
16221
16559
|
"no-generic-single-export-module": no_generic_single_export_module_default,
|
|
16222
16560
|
"no-offset-pagination": no_offset_pagination_default,
|
|
16223
16561
|
"no-positional-tuple-return": no_positional_tuple_return_default,
|
|
16562
|
+
"no-production-browser-source-maps": no_production_browser_source_maps_default,
|
|
16224
16563
|
"no-raw-env": no_raw_env_default,
|
|
16225
16564
|
"no-raw-fetch-outside-clients": no_raw_fetch_outside_clients_default,
|
|
16226
16565
|
"no-restricted-library-load": no_restricted_library_load_default,
|
|
@@ -16229,6 +16568,7 @@ var rules = {
|
|
|
16229
16568
|
"no-restated-comment": no_restated_comment_default,
|
|
16230
16569
|
"no-restated-jsdoc": no_restated_jsdoc_default,
|
|
16231
16570
|
"no-secret-in-log": no_secret_in_log_default,
|
|
16571
|
+
"no-server-env-in-client-component": no_server_env_in_client_component_default,
|
|
16232
16572
|
"no-select-star": no_select_star_default,
|
|
16233
16573
|
"no-sentinel-return-on-catch": no_sentinel_return_on_catch_default,
|
|
16234
16574
|
"no-silent-promise-catch": no_silent_promise_catch_default,
|
|
@@ -16244,6 +16584,8 @@ var rules = {
|
|
|
16244
16584
|
"no-unnecessary-use-client": no_unnecessary_use_client_default,
|
|
16245
16585
|
"no-unsafe-mock-casting": no_unsafe_mock_casting_default,
|
|
16246
16586
|
"no-zod-native-enum": no_zod_native_enum_default,
|
|
16587
|
+
"require-use-form-default-values": require_use_form_default_values_default,
|
|
16588
|
+
"require-use-server-in-actions-file": require_use_server_in_actions_file_default,
|
|
16247
16589
|
"test-loops-over-literal-cases": test_loops_over_literal_cases_default,
|
|
16248
16590
|
"test-phase-label-comment": test_phase_label_comment_default,
|
|
16249
16591
|
"prefer-constant-time-secret-compare": prefer_constant_time_secret_compare_default,
|
|
@@ -16274,7 +16616,7 @@ var rules = {
|
|
|
16274
16616
|
};
|
|
16275
16617
|
var meta = {
|
|
16276
16618
|
name: "@sarj/eslint-plugin",
|
|
16277
|
-
version: "15.
|
|
16619
|
+
version: "15.12.0"
|
|
16278
16620
|
};
|
|
16279
16621
|
var applicationOnlyRules = [
|
|
16280
16622
|
"no-restricted-library-load",
|
|
@@ -16283,10 +16625,15 @@ var applicationOnlyRules = [
|
|
|
16283
16625
|
];
|
|
16284
16626
|
var advisoryRules = [
|
|
16285
16627
|
"no-bare-return-from-test-catch",
|
|
16628
|
+
"no-dangerously-allow-svg",
|
|
16286
16629
|
"no-duplicate-lifecycle-refresh-listeners",
|
|
16630
|
+
"no-production-browser-source-maps",
|
|
16287
16631
|
"no-router-refresh-polling",
|
|
16632
|
+
"no-server-env-in-client-component",
|
|
16288
16633
|
"iac-source-coupled-test",
|
|
16289
16634
|
"repeated-static-call-cases",
|
|
16635
|
+
"require-use-form-default-values",
|
|
16636
|
+
"require-use-server-in-actions-file",
|
|
16290
16637
|
"source-coupled-test",
|
|
16291
16638
|
"test-phase-label-comment"
|
|
16292
16639
|
];
|
|
@@ -16306,17 +16653,20 @@ var recommendedRules = {
|
|
|
16306
16653
|
"@sarj/no-impossible-zod-literal-bounds": "error",
|
|
16307
16654
|
"@sarj/no-log-only-catch": "error",
|
|
16308
16655
|
"@sarj/no-bare-return-from-test-catch": "warn",
|
|
16656
|
+
"@sarj/no-dangerously-allow-svg": "warn",
|
|
16309
16657
|
"@sarj/no-duplicate-lifecycle-refresh-listeners": "warn",
|
|
16310
16658
|
"@sarj/no-long-comment": "error",
|
|
16311
16659
|
"@sarj/no-vague-suppression-description": "error",
|
|
16312
16660
|
"@sarj/no-generic-single-export-module": "error",
|
|
16313
16661
|
"@sarj/no-offset-pagination": "error",
|
|
16314
16662
|
"@sarj/no-positional-tuple-return": "error",
|
|
16663
|
+
"@sarj/no-production-browser-source-maps": "warn",
|
|
16315
16664
|
"@sarj/no-repeated-string-literal": "error",
|
|
16316
16665
|
"@sarj/no-router-refresh-polling": "warn",
|
|
16317
16666
|
"@sarj/no-restated-comment": "error",
|
|
16318
16667
|
"@sarj/no-restated-jsdoc": "error",
|
|
16319
16668
|
"@sarj/no-secret-in-log": "error",
|
|
16669
|
+
"@sarj/no-server-env-in-client-component": "warn",
|
|
16320
16670
|
"@sarj/no-select-star": "error",
|
|
16321
16671
|
"@sarj/no-sentinel-return-on-catch": "error",
|
|
16322
16672
|
"@sarj/no-silent-promise-catch": "error",
|
|
@@ -16350,6 +16700,8 @@ var recommendedRules = {
|
|
|
16350
16700
|
"@sarj/require-fetch-timeout": "error",
|
|
16351
16701
|
"@sarj/require-port-for-service": "error",
|
|
16352
16702
|
"@sarj/require-static-next-matcher": "error",
|
|
16703
|
+
"@sarj/require-use-form-default-values": "warn",
|
|
16704
|
+
"@sarj/require-use-server-in-actions-file": "warn",
|
|
16353
16705
|
"@sarj/require-zod-form-validation": "error",
|
|
16354
16706
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
16355
16707
|
"@sarj/stepdown": "error",
|
|
@@ -16374,12 +16726,14 @@ var strictRules = {
|
|
|
16374
16726
|
"@sarj/no-impossible-zod-literal-bounds": "error",
|
|
16375
16727
|
"@sarj/no-log-only-catch": "error",
|
|
16376
16728
|
"@sarj/no-bare-return-from-test-catch": "warn",
|
|
16729
|
+
"@sarj/no-dangerously-allow-svg": "warn",
|
|
16377
16730
|
"@sarj/no-duplicate-lifecycle-refresh-listeners": "warn",
|
|
16378
16731
|
"@sarj/no-long-comment": "error",
|
|
16379
16732
|
"@sarj/no-vague-suppression-description": "error",
|
|
16380
16733
|
"@sarj/no-generic-single-export-module": "error",
|
|
16381
16734
|
"@sarj/no-offset-pagination": "error",
|
|
16382
16735
|
"@sarj/no-positional-tuple-return": "error",
|
|
16736
|
+
"@sarj/no-production-browser-source-maps": "warn",
|
|
16383
16737
|
"@sarj/no-raw-env": "error",
|
|
16384
16738
|
"@sarj/no-raw-fetch-outside-clients": "error",
|
|
16385
16739
|
"@sarj/no-repeated-string-literal": "error",
|
|
@@ -16387,6 +16741,7 @@ var strictRules = {
|
|
|
16387
16741
|
"@sarj/no-restated-comment": "error",
|
|
16388
16742
|
"@sarj/no-restated-jsdoc": "error",
|
|
16389
16743
|
"@sarj/no-secret-in-log": "error",
|
|
16744
|
+
"@sarj/no-server-env-in-client-component": "warn",
|
|
16390
16745
|
"@sarj/no-select-star": "error",
|
|
16391
16746
|
"@sarj/no-sentinel-return-on-catch": "error",
|
|
16392
16747
|
"@sarj/no-silent-promise-catch": "error",
|
|
@@ -16421,6 +16776,8 @@ var strictRules = {
|
|
|
16421
16776
|
"@sarj/require-fetch-timeout": "error",
|
|
16422
16777
|
"@sarj/require-port-for-service": "error",
|
|
16423
16778
|
"@sarj/require-static-next-matcher": "error",
|
|
16779
|
+
"@sarj/require-use-form-default-values": "warn",
|
|
16780
|
+
"@sarj/require-use-server-in-actions-file": "warn",
|
|
16424
16781
|
"@sarj/require-zod-form-validation": "error",
|
|
16425
16782
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
16426
16783
|
"@sarj/stepdown": "error",
|