@yasainet/eslint 0.0.53 → 0.0.55
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/package.json +1 -1
- package/src/common/index.mjs +15 -5
- package/src/common/layers.mjs +22 -13
- package/src/common/local-plugins/index.mjs +4 -0
- package/src/common/local-plugins/supabase-columns-satisfies.mjs +70 -0
- package/src/common/local-plugins/supabase-select-typed-columns.mjs +95 -0
- package/src/common/naming.mjs +19 -0
- package/src/common/rules.mjs +109 -80
- package/src/deno/index.mjs +13 -1
package/package.json
CHANGED
package/src/common/index.mjs
CHANGED
|
@@ -3,20 +3,30 @@ import { createImportsConfigs } from "./imports.mjs";
|
|
|
3
3
|
import { createJsdocConfigs } from "./jsdoc.mjs";
|
|
4
4
|
import { createLayersConfigs } from "./layers.mjs";
|
|
5
5
|
import { createLibNamingConfigs, createNamingConfigs, createUtilsNamingConfigs } from "./naming.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { createRulesConfigs } from "./rules.mjs";
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Build common configs scoped to the given feature root:
|
|
10
|
+
*
|
|
11
|
+
* - `banAliasImports: true` enforces relative imports inside the feature root
|
|
12
|
+
* - `typeAware: false` disables type-aware rules and `local/no-any-return`,
|
|
13
|
+
* for environments without a project tsconfig (e.g., Deno entry)
|
|
14
|
+
* - `rulesFiles` narrows the parser/rules block. Pass when combining multiple
|
|
15
|
+
* entries so each entry only overrides its own files (e.g., the deno entry
|
|
16
|
+
* passes `["supabase/functions/**\/*.ts"]` so it doesn't override next's
|
|
17
|
+
* parser settings on `src/`).
|
|
18
|
+
*/
|
|
9
19
|
export function createCommonConfigs(
|
|
10
20
|
featureRoot,
|
|
11
|
-
{ banAliasImports = false } = {},
|
|
21
|
+
{ banAliasImports = false, typeAware = true, rulesFiles } = {},
|
|
12
22
|
) {
|
|
13
23
|
const prefixLibMapping = generatePrefixLibMapping(featureRoot);
|
|
14
24
|
return [
|
|
15
|
-
...
|
|
25
|
+
...createRulesConfigs({ typeAware, ...(rulesFiles && { files: rulesFiles }) }),
|
|
16
26
|
...createNamingConfigs(featureRoot, prefixLibMapping),
|
|
17
27
|
...createLibNamingConfigs(featureRoot),
|
|
18
28
|
...createUtilsNamingConfigs(featureRoot),
|
|
19
|
-
...createLayersConfigs(featureRoot),
|
|
29
|
+
...createLayersConfigs(featureRoot, { typeAware }),
|
|
20
30
|
...createImportsConfigs(featureRoot, prefixLibMapping, { banAliasImports }),
|
|
21
31
|
...createJsdocConfigs(featureRoot),
|
|
22
32
|
];
|
package/src/common/layers.mjs
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { localPlugin } from "./local-plugins/index.mjs";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Scope layer rules to the given feature root:
|
|
5
|
+
*
|
|
6
|
+
* - `typeAware: true` (default) includes `layers/no-any-return`, which uses
|
|
7
|
+
* the TypeScript checker to inspect inferred return types
|
|
8
|
+
* - `typeAware: false` skips it for environments where the checker cannot run
|
|
9
|
+
* (e.g., Deno files outside the project tsconfig)
|
|
10
|
+
*/
|
|
11
|
+
export function createLayersConfigs(featureRoot, { typeAware = true } = {}) {
|
|
5
12
|
const loggerSelector = "CallExpression[callee.object.name='logger']";
|
|
6
13
|
const loggerMessage =
|
|
7
14
|
"logger is not allowed outside interactors. Logging belongs in interactors.";
|
|
8
15
|
|
|
16
|
+
const noAnyReturnConfig = {
|
|
17
|
+
name: "layers/no-any-return",
|
|
18
|
+
files: [
|
|
19
|
+
`${featureRoot}/**/queries/*.ts`,
|
|
20
|
+
`${featureRoot}/**/services/*.ts`,
|
|
21
|
+
],
|
|
22
|
+
plugins: { local: localPlugin },
|
|
23
|
+
rules: {
|
|
24
|
+
"local/no-any-return": "error",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
9
28
|
return [
|
|
10
29
|
// Logger/console: all features except interactors
|
|
11
30
|
{
|
|
@@ -74,17 +93,7 @@ export function createLayersConfigs(featureRoot) {
|
|
|
74
93
|
// Boundary type safety: queries & services must not leak `any`
|
|
75
94
|
// into their public API. Uses type-aware inspection of the inferred
|
|
76
95
|
// return type so unannotated functions are still checked.
|
|
77
|
-
|
|
78
|
-
name: "layers/no-any-return",
|
|
79
|
-
files: [
|
|
80
|
-
`${featureRoot}/**/queries/*.ts`,
|
|
81
|
-
`${featureRoot}/**/services/*.ts`,
|
|
82
|
-
],
|
|
83
|
-
plugins: { local: localPlugin },
|
|
84
|
-
rules: {
|
|
85
|
-
"local/no-any-return": "error",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
96
|
+
...(typeAware ? [noAnyReturnConfig] : []),
|
|
88
97
|
// Services: try-catch + logger + dead error fallbacks
|
|
89
98
|
{
|
|
90
99
|
name: "layers/services",
|
|
@@ -6,6 +6,8 @@ import { noAnyReturnRule } from "./no-any-return.mjs";
|
|
|
6
6
|
import { queriesExportRule } from "./queries-export.mjs";
|
|
7
7
|
import { queriesNamespaceImportRule } from "./queries-namespace-import.mjs";
|
|
8
8
|
import { schemaNamingRule } from "./schema-naming.mjs";
|
|
9
|
+
import { supabaseColumnsSatisfiesRule } from "./supabase-columns-satisfies.mjs";
|
|
10
|
+
import { supabaseSelectTypedColumnsRule } from "./supabase-select-typed-columns.mjs";
|
|
9
11
|
|
|
10
12
|
/** Single plugin object to avoid ESLint "Cannot redefine plugin" errors. */
|
|
11
13
|
export const localPlugin = {
|
|
@@ -18,5 +20,7 @@ export const localPlugin = {
|
|
|
18
20
|
"queries-export": queriesExportRule,
|
|
19
21
|
"queries-namespace-import": queriesNamespaceImportRule,
|
|
20
22
|
"schema-naming": schemaNamingRule,
|
|
23
|
+
"supabase-columns-satisfies": supabaseColumnsSatisfiesRule,
|
|
24
|
+
"supabase-select-typed-columns": supabaseSelectTypedColumnsRule,
|
|
21
25
|
},
|
|
22
26
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforce `[...] as const satisfies readonly (keyof Tables<"...">)[]` for
|
|
3
|
+
* `*_COLUMNS` constant declarations.
|
|
4
|
+
*
|
|
5
|
+
* Apply to `**\/queries/*.query.ts`. Without `as const satisfies`, typos
|
|
6
|
+
* in column names slip past the lint phase — Supabase only complains at
|
|
7
|
+
* runtime when the query executes. The `satisfies` annotation forces
|
|
8
|
+
* TypeScript to validate every entry against the schema before the code
|
|
9
|
+
* ever runs. The actual `keyof Tables<"...">` content is left to the type
|
|
10
|
+
* checker; the lint rule only verifies the syntactic shape.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const COLUMNS_NAME = /^[A-Z][A-Z0-9_]*_COLUMNS$/;
|
|
14
|
+
|
|
15
|
+
/** Unwrap nested type assertions and find the underlying expression. */
|
|
16
|
+
function unwrapTypeAssertions(node) {
|
|
17
|
+
let current = node;
|
|
18
|
+
while (
|
|
19
|
+
current &&
|
|
20
|
+
(current.type === "TSAsExpression" ||
|
|
21
|
+
current.type === "TSSatisfiesExpression")
|
|
22
|
+
) {
|
|
23
|
+
current = current.expression;
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isAsConstSatisfies(initNode) {
|
|
29
|
+
if (!initNode) return false;
|
|
30
|
+
if (initNode.type !== "TSSatisfiesExpression") return false;
|
|
31
|
+
const inner = initNode.expression;
|
|
32
|
+
if (inner.type !== "TSAsExpression") return false;
|
|
33
|
+
const ann = inner.typeAnnotation;
|
|
34
|
+
if (!ann) return false;
|
|
35
|
+
if (ann.type !== "TSTypeReference") return false;
|
|
36
|
+
if (ann.typeName.type !== "Identifier") return false;
|
|
37
|
+
if (ann.typeName.name !== "const") return false;
|
|
38
|
+
if (inner.expression.type !== "ArrayExpression") return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const supabaseColumnsSatisfiesRule = {
|
|
43
|
+
meta: {
|
|
44
|
+
type: "problem",
|
|
45
|
+
messages: {
|
|
46
|
+
missing:
|
|
47
|
+
'Column constant `{{ name }}` must use `[...] as const satisfies readonly (keyof Tables<"table">)[]` so column names are validated against the schema at compile time.',
|
|
48
|
+
},
|
|
49
|
+
schema: [],
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
return {
|
|
53
|
+
VariableDeclarator(node) {
|
|
54
|
+
if (node.id.type !== "Identifier") return;
|
|
55
|
+
if (!COLUMNS_NAME.test(node.id.name)) return;
|
|
56
|
+
// Only enforce on array initializers. String literals like
|
|
57
|
+
// POST_UPSERT_CONFLICT_COLUMNS are PostgREST conflict-target specs,
|
|
58
|
+
// not column lists, and are out of scope.
|
|
59
|
+
const inner = unwrapTypeAssertions(node.init);
|
|
60
|
+
if (!inner || inner.type !== "ArrayExpression") return;
|
|
61
|
+
if (isAsConstSatisfies(node.init)) return;
|
|
62
|
+
context.report({
|
|
63
|
+
node: node.id,
|
|
64
|
+
messageId: "missing",
|
|
65
|
+
data: { name: node.id.name },
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforce typed column constants for Supabase `.select()` calls.
|
|
3
|
+
*
|
|
4
|
+
* Apply to `**\/queries/*.query.ts`. Forces `.select()` to take the form
|
|
5
|
+
* `joinColumns(<X_COLUMNS>)` where `X_COLUMNS` is an UPPER_SNAKE identifier
|
|
6
|
+
* ending with `_COLUMNS`. The identifier is expected to be declared with
|
|
7
|
+
* `as const satisfies readonly (keyof Tables<"table">)[]` so the column
|
|
8
|
+
* names are validated against the schema at compile time. The `satisfies`
|
|
9
|
+
* shape itself is enforced by the companion `supabase-columns-satisfies`
|
|
10
|
+
* rule.
|
|
11
|
+
*
|
|
12
|
+
* `joinColumns()` is a project-supplied helper that comma-joins a const
|
|
13
|
+
* string tuple while preserving the literal string type so Supabase's
|
|
14
|
+
* `.select()` type parser can infer the row shape (a plain `.join(",")`
|
|
15
|
+
* widens to `string` and breaks inference).
|
|
16
|
+
*
|
|
17
|
+
* Banned:
|
|
18
|
+
* .select() implicit "all columns"
|
|
19
|
+
* .select("*") silent exposure of new schema columns
|
|
20
|
+
* .select("id, name") inline literal, invisible to grep
|
|
21
|
+
* .select(`${x}, y`) dynamic concatenation
|
|
22
|
+
* .select(POST_LIST_COLUMNS.join(",")) plain .join widens to `string`, breaks inference
|
|
23
|
+
* .select(someVar) non-conforming variable
|
|
24
|
+
*
|
|
25
|
+
* Allowed:
|
|
26
|
+
* .select(joinColumns(POST_LIST_COLUMNS)) typed constant via project helper
|
|
27
|
+
*
|
|
28
|
+
* Why: column lists must be (1) named for grep / review, (2) checked
|
|
29
|
+
* against the schema, (3) never silently grow on schema additions.
|
|
30
|
+
* For column-level access control, use Postgres views (`from("posts_public")`).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const COLUMNS_NAME = /^[A-Z][A-Z0-9_]*_COLUMNS$/;
|
|
34
|
+
|
|
35
|
+
function asJoinColumnsCall(arg) {
|
|
36
|
+
if (!arg) return null;
|
|
37
|
+
if (arg.type !== "CallExpression") return null;
|
|
38
|
+
if (arg.callee.type !== "Identifier") return null;
|
|
39
|
+
if (arg.callee.name !== "joinColumns") return null;
|
|
40
|
+
if (arg.arguments.length !== 1) return null;
|
|
41
|
+
if (arg.arguments[0].type !== "Identifier") return null;
|
|
42
|
+
return arg.arguments[0];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const supabaseSelectTypedColumnsRule = {
|
|
46
|
+
meta: {
|
|
47
|
+
type: "problem",
|
|
48
|
+
messages: {
|
|
49
|
+
noArgs:
|
|
50
|
+
"Empty `.select()` returns all columns implicitly. Pass `joinColumns(<X_COLUMNS>)` where X_COLUMNS is a typed constant.",
|
|
51
|
+
literalArg:
|
|
52
|
+
'Inline `.select()` argument is forbidden. Define `const X_COLUMNS = [...] as const satisfies readonly (keyof Tables<"table">)[];` and call `.select(joinColumns(X_COLUMNS))`. Use Postgres views for column-level access control.',
|
|
53
|
+
shapeArg:
|
|
54
|
+
'`.select()` argument must be `joinColumns(<X_COLUMNS>)`. Other expressions defeat type inference and column-level review.',
|
|
55
|
+
naming:
|
|
56
|
+
"Column constant `{{ name }}` must be UPPER_SNAKE_CASE ending with `_COLUMNS` (e.g. POST_LIST_COLUMNS, POST_DETAIL_COLUMNS).",
|
|
57
|
+
},
|
|
58
|
+
schema: [],
|
|
59
|
+
},
|
|
60
|
+
create(context) {
|
|
61
|
+
return {
|
|
62
|
+
CallExpression(node) {
|
|
63
|
+
if (node.callee.type !== "MemberExpression") return;
|
|
64
|
+
if (node.callee.property.type !== "Identifier") return;
|
|
65
|
+
if (node.callee.property.name !== "select") return;
|
|
66
|
+
|
|
67
|
+
if (node.arguments.length === 0) {
|
|
68
|
+
context.report({ node, messageId: "noArgs" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const arg = node.arguments[0];
|
|
73
|
+
|
|
74
|
+
if (arg.type === "Literal" || arg.type === "TemplateLiteral") {
|
|
75
|
+
context.report({ node: arg, messageId: "literalArg" });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const id = asJoinColumnsCall(arg);
|
|
80
|
+
if (!id) {
|
|
81
|
+
context.report({ node: arg, messageId: "shapeArg" });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!COLUMNS_NAME.test(id.name)) {
|
|
86
|
+
context.report({
|
|
87
|
+
node: id,
|
|
88
|
+
messageId: "naming",
|
|
89
|
+
data: { name: id.name },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
package/src/common/naming.mjs
CHANGED
|
@@ -118,6 +118,25 @@ export function createNamingConfigs(featureRoot, prefixLibMapping) {
|
|
|
118
118
|
"local/queries-namespace-import": "error",
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
|
+
{
|
|
122
|
+
name: "naming/supabase-select",
|
|
123
|
+
files: featuresGlob(featureRoot, "**/queries/*.query.ts"),
|
|
124
|
+
plugins: { local: localPlugin },
|
|
125
|
+
rules: {
|
|
126
|
+
"local/supabase-select-typed-columns": "error",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "naming/supabase-columns-satisfies",
|
|
131
|
+
files: [
|
|
132
|
+
...featuresGlob(featureRoot, "**/queries/*.query.ts"),
|
|
133
|
+
...featuresGlob(featureRoot, "**/constants/*.constant.ts"),
|
|
134
|
+
],
|
|
135
|
+
plugins: { local: localPlugin },
|
|
136
|
+
rules: {
|
|
137
|
+
"local/supabase-columns-satisfies": "error",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
121
140
|
{
|
|
122
141
|
name: "naming/form-state",
|
|
123
142
|
files: featuresGlob(featureRoot, "**/*.ts"),
|
package/src/common/rules.mjs
CHANGED
|
@@ -23,87 +23,116 @@ const findProjectRoot = (start) => {
|
|
|
23
23
|
|
|
24
24
|
const projectRoot = findProjectRoot(import.meta.dirname);
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"@stylistic": stylistic,
|
|
32
|
-
"simple-import-sort": simpleImportSortPlugin,
|
|
33
|
-
},
|
|
34
|
-
rules: {
|
|
35
|
-
"no-console": "warn",
|
|
36
|
-
"no-irregular-whitespace": [
|
|
37
|
-
"warn",
|
|
38
|
-
{
|
|
39
|
-
skipStrings: false,
|
|
40
|
-
skipComments: false,
|
|
41
|
-
skipRegExps: false,
|
|
42
|
-
skipTemplates: false,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
"simple-import-sort/imports": "warn",
|
|
46
|
-
"simple-import-sort/exports": "warn",
|
|
47
|
-
"@stylistic/quotes": ["warn", "double", { avoidEscape: true }],
|
|
48
|
-
// Dead code detection: rules with no legitimate use case, so always safe to error.
|
|
49
|
-
"no-unreachable": "error",
|
|
50
|
-
"no-unreachable-loop": "error",
|
|
51
|
-
"no-useless-return": "error",
|
|
52
|
-
"no-constant-condition": "error",
|
|
53
|
-
"no-constant-binary-expression": "error",
|
|
54
|
-
"no-dupe-else-if": "error",
|
|
55
|
-
"no-self-assign": "error",
|
|
56
|
-
"no-self-compare": "error",
|
|
57
|
-
"no-useless-catch": "error",
|
|
58
|
-
"no-fallthrough": "error",
|
|
59
|
-
},
|
|
26
|
+
const sharedRulesConfig = {
|
|
27
|
+
name: "rules/shared",
|
|
28
|
+
plugins: {
|
|
29
|
+
"@stylistic": stylistic,
|
|
30
|
+
"simple-import-sort": simpleImportSortPlugin,
|
|
60
31
|
},
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
tsconfigRootDir: projectRoot,
|
|
32
|
+
rules: {
|
|
33
|
+
"no-console": "warn",
|
|
34
|
+
"no-irregular-whitespace": [
|
|
35
|
+
"warn",
|
|
36
|
+
{
|
|
37
|
+
skipStrings: false,
|
|
38
|
+
skipComments: false,
|
|
39
|
+
skipRegExps: false,
|
|
40
|
+
skipTemplates: false,
|
|
71
41
|
},
|
|
42
|
+
],
|
|
43
|
+
"simple-import-sort/imports": "warn",
|
|
44
|
+
"simple-import-sort/exports": "warn",
|
|
45
|
+
"@stylistic/quotes": ["warn", "double", { avoidEscape: true }],
|
|
46
|
+
// Dead code detection: rules with no legitimate use case, so always safe to error.
|
|
47
|
+
"no-unreachable": "error",
|
|
48
|
+
"no-unreachable-loop": "error",
|
|
49
|
+
"no-useless-return": "error",
|
|
50
|
+
"no-constant-condition": "error",
|
|
51
|
+
"no-constant-binary-expression": "error",
|
|
52
|
+
"no-dupe-else-if": "error",
|
|
53
|
+
"no-self-assign": "error",
|
|
54
|
+
"no-self-compare": "error",
|
|
55
|
+
"no-useless-catch": "error",
|
|
56
|
+
"no-fallthrough": "error",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const syntacticTypeScriptRules = {
|
|
61
|
+
"@typescript-eslint/no-unused-vars": [
|
|
62
|
+
"error",
|
|
63
|
+
{
|
|
64
|
+
argsIgnorePattern: "^_",
|
|
65
|
+
varsIgnorePattern: "^_",
|
|
66
|
+
caughtErrorsIgnorePattern: "^_",
|
|
72
67
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
68
|
+
],
|
|
69
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
70
|
+
"error",
|
|
71
|
+
{ prefer: "type-imports" },
|
|
72
|
+
],
|
|
73
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const typeAwareTypeScriptRules = {
|
|
77
|
+
// Detect defensive fallbacks on non-nullable values (e.g., `?? ''`
|
|
78
|
+
// on a non-null column). Promoted to error once consuming projects
|
|
79
|
+
// (bitcomic.net, getpayme.net) reached 0 warnings.
|
|
80
|
+
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
81
|
+
// Type-aware async safety: silent await omissions are a leading cause
|
|
82
|
+
// of race conditions in server actions and background tasks.
|
|
83
|
+
"@typescript-eslint/no-floating-promises": "error",
|
|
84
|
+
"@typescript-eslint/no-misused-promises": "error",
|
|
85
|
+
"@typescript-eslint/await-thenable": "error",
|
|
86
|
+
"@typescript-eslint/require-await": "error",
|
|
87
|
+
// Type-aware `any` propagation checks: any が境界を越えた瞬間に
|
|
88
|
+
// 残りのコードで型検査が無効化されるため、検出したら確実に止める。
|
|
89
|
+
"@typescript-eslint/no-unsafe-assignment": "error",
|
|
90
|
+
"@typescript-eslint/no-unsafe-call": "error",
|
|
91
|
+
"@typescript-eslint/no-unsafe-member-access": "error",
|
|
92
|
+
"@typescript-eslint/no-unsafe-argument": "error",
|
|
93
|
+
"@typescript-eslint/no-unsafe-return": "error",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const typeAwareRulesOff = Object.fromEntries(
|
|
97
|
+
Object.keys(typeAwareTypeScriptRules).map((rule) => [rule, "off"]),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build base rule configs:
|
|
102
|
+
*
|
|
103
|
+
* - `typeAware: true` (default) enables `projectService` and type-aware rules
|
|
104
|
+
* (`no-unnecessary-condition`, `no-floating-promises`, `no-unsafe-*`, etc.)
|
|
105
|
+
* for the matched `files`
|
|
106
|
+
* - `typeAware: false` disables `projectService` and forces type-aware rules
|
|
107
|
+
* off for the matched `files`. Use for files outside the project tsconfig
|
|
108
|
+
* (e.g., Deno files in Supabase Edge Functions)
|
|
109
|
+
*
|
|
110
|
+
* `files` defaults to all TypeScript sources. When combining multiple entries
|
|
111
|
+
* (e.g., next + deno), pass a narrow pattern so the type-aware override only
|
|
112
|
+
* applies to its target files.
|
|
113
|
+
*/
|
|
114
|
+
export function createRulesConfigs({
|
|
115
|
+
typeAware = true,
|
|
116
|
+
files = ["**/*.ts", "**/*.tsx"],
|
|
117
|
+
} = {}) {
|
|
118
|
+
return [
|
|
119
|
+
sharedRulesConfig,
|
|
120
|
+
{
|
|
121
|
+
name: "rules/typescript",
|
|
122
|
+
files,
|
|
123
|
+
languageOptions: {
|
|
124
|
+
parser: tseslint.parser,
|
|
125
|
+
parserOptions: typeAware
|
|
126
|
+
? { projectService: true, tsconfigRootDir: projectRoot }
|
|
127
|
+
: { projectService: false, project: null },
|
|
128
|
+
},
|
|
129
|
+
plugins: {
|
|
130
|
+
"@typescript-eslint": tseslint.plugin,
|
|
131
|
+
},
|
|
132
|
+
rules: {
|
|
133
|
+
...syntacticTypeScriptRules,
|
|
134
|
+
...(typeAware ? typeAwareTypeScriptRules : typeAwareRulesOff),
|
|
135
|
+
},
|
|
107
136
|
},
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
];
|
|
138
|
+
}
|
package/src/deno/index.mjs
CHANGED
|
@@ -9,9 +9,21 @@ const denoEntryPointConfigs = createEntryPointConfigs(
|
|
|
9
9
|
["supabase/functions/_*/**"],
|
|
10
10
|
);
|
|
11
11
|
|
|
12
|
+
// Deno files are not covered by the consumer's project tsconfig
|
|
13
|
+
// (Supabase functions live outside Next.js's tsconfig), so type-aware
|
|
14
|
+
// rules cannot consult the type checker. Disable them here and rely on
|
|
15
|
+
// `deno check` / `deno lint` for type-level guarantees.
|
|
16
|
+
//
|
|
17
|
+
// `rulesFiles` is narrowed so the override only affects Deno files. When this
|
|
18
|
+
// entry is combined with `next`/`node`, those entries keep their type-aware
|
|
19
|
+
// settings on their own files.
|
|
12
20
|
/** Deno ESLint flat config entry point. */
|
|
13
21
|
export const eslintConfig = [
|
|
14
|
-
...createCommonConfigs(FEATURE_ROOT, {
|
|
22
|
+
...createCommonConfigs(FEATURE_ROOT, {
|
|
23
|
+
banAliasImports: true,
|
|
24
|
+
typeAware: false,
|
|
25
|
+
rulesFiles: ["supabase/functions/**/*.ts"],
|
|
26
|
+
}),
|
|
15
27
|
...denoImportsConfigs,
|
|
16
28
|
...denoEntryPointConfigs,
|
|
17
29
|
];
|