@yasainet/eslint 0.0.33 → 0.0.35
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
CHANGED
|
@@ -3,7 +3,8 @@ import path from "path";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Extract table names from Supabase generated types file.
|
|
6
|
-
* Looks for keys
|
|
6
|
+
* Looks for top-level keys under `Tables: {` inside the `public` schema.
|
|
7
|
+
* Uses brace counting to handle deeply nested type definitions.
|
|
7
8
|
*/
|
|
8
9
|
function extractTableNames(supabaseTypePath) {
|
|
9
10
|
if (!fs.existsSync(supabaseTypePath)) {
|
|
@@ -11,17 +12,57 @@ function extractTableNames(supabaseTypePath) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const content = fs.readFileSync(supabaseTypePath, "utf-8");
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
// Find `public:` excluding `graphql_public:` via negative lookbehind
|
|
17
|
+
const publicMatch = /(?<!\w)public:\s*\{/.exec(content);
|
|
18
|
+
if (!publicMatch) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tablesLabel = "Tables:";
|
|
23
|
+
const tablesIdx = content.indexOf(tablesLabel, publicMatch.index);
|
|
24
|
+
if (tablesIdx === -1) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Find the opening brace of `Tables: {`
|
|
29
|
+
const braceStart = content.indexOf("{", tablesIdx + tablesLabel.length);
|
|
30
|
+
if (braceStart === -1) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Extract the Tables block using brace counting
|
|
35
|
+
let depth = 0;
|
|
36
|
+
let blockEnd = -1;
|
|
37
|
+
for (let i = braceStart; i < content.length; i++) {
|
|
38
|
+
if (content[i] === "{") depth++;
|
|
39
|
+
else if (content[i] === "}") depth--;
|
|
40
|
+
if (depth === 0) {
|
|
41
|
+
blockEnd = i;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (blockEnd === -1) {
|
|
16
46
|
return [];
|
|
17
47
|
}
|
|
18
48
|
|
|
19
|
-
|
|
20
|
-
const
|
|
49
|
+
// Extract top-level keys (depth 0) inside the Tables block
|
|
50
|
+
const tablesBlock = content.slice(braceStart + 1, blockEnd);
|
|
21
51
|
const names = [];
|
|
52
|
+
depth = 0;
|
|
53
|
+
const keyRegex = /(\w+)\s*:/g;
|
|
22
54
|
let match;
|
|
23
55
|
while ((match = keyRegex.exec(tablesBlock)) !== null) {
|
|
24
|
-
|
|
56
|
+
// Count braces before this match to determine depth
|
|
57
|
+
const preceding = tablesBlock.slice(0, match.index);
|
|
58
|
+
let d = 0;
|
|
59
|
+
for (const ch of preceding) {
|
|
60
|
+
if (ch === "{") d++;
|
|
61
|
+
else if (ch === "}") d--;
|
|
62
|
+
}
|
|
63
|
+
if (d === 0) {
|
|
64
|
+
names.push(match[1]);
|
|
65
|
+
}
|
|
25
66
|
}
|
|
26
67
|
return names;
|
|
27
68
|
}
|
|
@@ -2,6 +2,7 @@ import { actionHandleServiceRule } from "./action-handle-service.mjs";
|
|
|
2
2
|
import { featureNameRule } from "./feature-name.mjs";
|
|
3
3
|
import { importPathStyleRule } from "./import-path-style.mjs";
|
|
4
4
|
import { namespaceImportNameRule } from "./namespace-import-name.mjs";
|
|
5
|
+
import { schemaNamingRule } from "./schema-naming.mjs";
|
|
5
6
|
|
|
6
7
|
/** Single plugin object to avoid ESLint "Cannot redefine plugin" errors. */
|
|
7
8
|
export const localPlugin = {
|
|
@@ -10,5 +11,6 @@ export const localPlugin = {
|
|
|
10
11
|
"feature-name": featureNameRule,
|
|
11
12
|
"import-path-style": importPathStyleRule,
|
|
12
13
|
"namespace-import-name": namespaceImportNameRule,
|
|
14
|
+
"schema-naming": schemaNamingRule,
|
|
13
15
|
},
|
|
14
16
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforce that exported variables in schema files use camelCase with a "Schema" suffix.
|
|
3
|
+
* e.g., `export const userSchema = z.object(...)` is valid.
|
|
4
|
+
* `export const UserSchema = ...` or `export const user = ...` are invalid.
|
|
5
|
+
* `export type` declarations are ignored (used for `z.infer` types).
|
|
6
|
+
*/
|
|
7
|
+
export const schemaNamingRule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
messages: {
|
|
11
|
+
missingSuffix:
|
|
12
|
+
"Exported variable '{{ name }}' in a schema file must end with 'Schema'.",
|
|
13
|
+
invalidCasing:
|
|
14
|
+
"Exported variable '{{ name }}' must be camelCase (start with a lowercase letter).",
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
ExportNamedDeclaration(node) {
|
|
21
|
+
if (!node.declaration || node.declaration.type !== "VariableDeclaration") {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const declarator of node.declaration.declarations) {
|
|
26
|
+
const name = declarator.id.name;
|
|
27
|
+
|
|
28
|
+
if (!name.endsWith("Schema")) {
|
|
29
|
+
context.report({
|
|
30
|
+
node: declarator.id,
|
|
31
|
+
messageId: "missingSuffix",
|
|
32
|
+
data: { name },
|
|
33
|
+
});
|
|
34
|
+
} else if (name[0] !== name[0].toLowerCase()) {
|
|
35
|
+
context.report({
|
|
36
|
+
node: declarator.id,
|
|
37
|
+
messageId: "invalidCasing",
|
|
38
|
+
data: { name },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
package/src/common/naming.mjs
CHANGED
|
@@ -167,6 +167,14 @@ export function createNamingConfigs(featureRoot, prefixLibMapping) {
|
|
|
167
167
|
],
|
|
168
168
|
},
|
|
169
169
|
},
|
|
170
|
+
{
|
|
171
|
+
name: "naming/schema-naming",
|
|
172
|
+
files: featuresGlob(featureRoot, "**/schemas/*.schema.ts"),
|
|
173
|
+
plugins: { local: localPlugin },
|
|
174
|
+
rules: {
|
|
175
|
+
"local/schema-naming": "error",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
170
178
|
{
|
|
171
179
|
name: "naming/schemas-shared",
|
|
172
180
|
files: featuresGlob(featureRoot, "shared/schemas/*.ts"),
|