@yasainet/eslint 0.0.31 → 0.0.33

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasainet/eslint",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "ESLint",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,92 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Extract table names from Supabase generated types file.
6
+ * Looks for keys directly under `Tables: {` in the Database interface.
7
+ */
8
+ function extractTableNames(supabaseTypePath) {
9
+ if (!fs.existsSync(supabaseTypePath)) {
10
+ return [];
11
+ }
12
+
13
+ const content = fs.readFileSync(supabaseTypePath, "utf-8");
14
+ const tablesMatch = content.match(/Tables:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/);
15
+ if (!tablesMatch) {
16
+ return [];
17
+ }
18
+
19
+ const tablesBlock = tablesMatch[1];
20
+ const keyRegex = /^\s*(\w+)\s*:/gm;
21
+ const names = [];
22
+ let match;
23
+ while ((match = keyRegex.exec(tablesBlock)) !== null) {
24
+ names.push(match[1]);
25
+ }
26
+ return names;
27
+ }
28
+
29
+ /** Convert snake_case to kebab-case. */
30
+ function toKebab(name) {
31
+ return name.replace(/_/g, "-");
32
+ }
33
+
34
+ /**
35
+ * Enforce that feature directory names match allowed values:
36
+ * "shared", "auth", plus Supabase table names converted to kebab-case.
37
+ */
38
+ export const featureNameRule = {
39
+ meta: {
40
+ type: "problem",
41
+ messages: {
42
+ invalidFeatureName:
43
+ "Feature directory '{{ name }}' is not allowed. Allowed: {{ allowed }}.",
44
+ },
45
+ schema: [
46
+ {
47
+ type: "object",
48
+ properties: {
49
+ featureRoot: { type: "string" },
50
+ },
51
+ required: ["featureRoot"],
52
+ additionalProperties: false,
53
+ },
54
+ ],
55
+ },
56
+ create(context) {
57
+ const { featureRoot } = context.options[0];
58
+ const filename = context.filename;
59
+
60
+ const rootSep = featureRoot + "/";
61
+ const rootIdx = filename.indexOf(rootSep);
62
+ if (rootIdx === -1) return {};
63
+
64
+ const afterRoot = filename.slice(rootIdx + rootSep.length);
65
+ const featureName = afterRoot.split("/")[0];
66
+ if (!featureName) return {};
67
+
68
+ const projectRoot = filename.slice(0, rootIdx).replace(/\/src$/, "");
69
+ const supabaseTypePath = path.join(
70
+ projectRoot,
71
+ featureRoot.replace(/features$/, "lib/supabase/supabase.type.ts"),
72
+ );
73
+
74
+ const tableNames = extractTableNames(supabaseTypePath);
75
+ const allowedNames = ["shared", "auth", ...tableNames.map(toKebab)];
76
+
77
+ if (allowedNames.includes(featureName)) return {};
78
+
79
+ return {
80
+ Program(node) {
81
+ context.report({
82
+ node,
83
+ messageId: "invalidFeatureName",
84
+ data: {
85
+ name: featureName,
86
+ allowed: allowedNames.join(", "),
87
+ },
88
+ });
89
+ },
90
+ };
91
+ },
92
+ };
@@ -1,4 +1,5 @@
1
1
  import { actionHandleServiceRule } from "./action-handle-service.mjs";
2
+ import { featureNameRule } from "./feature-name.mjs";
2
3
  import { importPathStyleRule } from "./import-path-style.mjs";
3
4
  import { namespaceImportNameRule } from "./namespace-import-name.mjs";
4
5
 
@@ -6,6 +7,7 @@ import { namespaceImportNameRule } from "./namespace-import-name.mjs";
6
7
  export const localPlugin = {
7
8
  rules: {
8
9
  "action-handle-service": actionHandleServiceRule,
10
+ "feature-name": featureNameRule,
9
11
  "import-path-style": importPathStyleRule,
10
12
  "namespace-import-name": namespaceImportNameRule,
11
13
  },
@@ -51,7 +51,7 @@ function parseImportSource(importPath, featureRoot) {
51
51
  if (segments.length < 2) return null;
52
52
 
53
53
  const featureDir = segments[0];
54
- const fileName = segments[segments.length - 1].replace(/\.tsx?$/, "");
54
+ const fileName = segments[segments.length - 1].replace(/\.[jt]sx?$/, "");
55
55
 
56
56
  const dotIdx = fileName.indexOf(".");
57
57
  if (dotIdx === -1) return null;
@@ -59,6 +59,15 @@ export function createNamingConfigs(featureRoot, prefixLibMapping) {
59
59
 
60
60
  const configs = [];
61
61
 
62
+ configs.push({
63
+ name: "naming/feature-name",
64
+ files: featuresGlob(featureRoot, "**/*.ts"),
65
+ plugins: { local: localPlugin },
66
+ rules: {
67
+ "local/feature-name": ["error", { featureRoot }],
68
+ },
69
+ });
70
+
62
71
  configs.push({
63
72
  name: "naming/namespace-import-name",
64
73
  files: featuresGlob(featureRoot, "**/*.ts"),