@gem-sdk/eslint-plugin 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,13 +1,15 @@
1
- # eslint-plugin-gem-fe
1
+ # @gem-sdk/eslint-plugin
2
2
 
3
3
  Shared ESLint rules for all GemPages frontend repositories.
4
4
 
5
5
  Consolidates every custom rule from `web-builder-shopify-app`, `web-builder`, and `web-builder-elements` into a single publishable npm package. The architecture rules support both React (`.tsx`) and Vue (`.vue`) files.
6
6
 
7
+ > The package name is `@gem-sdk/eslint-plugin`, so its ESLint plugin prefix is **`@gem-sdk`** (scoped packages drop the `eslint-plugin-` segment). Reference rules as `@gem-sdk/<rule>` and shareable configs as `plugin:@gem-sdk/<config>`.
8
+
7
9
  ## Installation
8
10
 
9
11
  ```bash
10
- yarn add -D eslint-plugin-gem-fe
12
+ yarn add -D @gem-sdk/eslint-plugin
11
13
  ```
12
14
 
13
15
  ## Usage
@@ -18,7 +20,7 @@ Choose the config that matches your tech stack:
18
20
 
19
21
  ```json
20
22
  {
21
- "extends": ["plugin:gem-fe/react"]
23
+ "extends": ["plugin:@gem-sdk/react"]
22
24
  }
23
25
  ```
24
26
 
@@ -26,7 +28,7 @@ Choose the config that matches your tech stack:
26
28
 
27
29
  ```json
28
30
  {
29
- "extends": ["plugin:gem-fe/vue"]
31
+ "extends": ["plugin:@gem-sdk/vue"]
30
32
  }
31
33
  ```
32
34
 
@@ -34,7 +36,7 @@ Choose the config that matches your tech stack:
34
36
 
35
37
  ```json
36
38
  {
37
- "extends": ["plugin:gem-fe/recommended"]
39
+ "extends": ["plugin:@gem-sdk/recommended"]
38
40
  }
39
41
  ```
40
42
 
@@ -42,18 +44,41 @@ Choose the config that matches your tech stack:
42
44
 
43
45
  ```json
44
46
  {
45
- "plugins": ["gem-fe"],
47
+ "plugins": ["@gem-sdk"],
46
48
  "rules": {
47
- "gem-fe/module-structure": "error",
48
- "gem-fe/no-module-barrel": "error",
49
- "gem-fe/module-import-boundary": "error",
50
- "gem-fe/pure-ui-layer": "error",
51
- "gem-fe/module-index-required": "error",
52
- "gem-fe/no-t-for-variable": "error",
53
- "gem-fe/not-use-store-to-refs": "error",
54
- "gem-fe/not-use-custom-color-class": "warn",
55
- "gem-fe/no-export-type-from-tsx": "error",
56
- "gem-fe/no-import-from-package-path": "error"
49
+ "@gem-sdk/module-structure": "error",
50
+ "@gem-sdk/no-module-barrel": "error",
51
+ "@gem-sdk/module-import-boundary": "error",
52
+ "@gem-sdk/pure-ui-layer": "error",
53
+ "@gem-sdk/module-index-required": "error",
54
+ "@gem-sdk/no-t-for-variable": "error",
55
+ "@gem-sdk/not-use-store-to-refs": "error",
56
+ "@gem-sdk/not-use-custom-color-class": "warn",
57
+ "@gem-sdk/no-export-type-from-component": "error",
58
+ "@gem-sdk/no-export-type-from-tsx": "error",
59
+ "@gem-sdk/no-import-from-package-path": "error",
60
+ "@gem-sdk/no-multiple-component-exports": "error"
57
61
  }
58
62
  }
59
63
  ```
64
+
65
+ ## What each config enables
66
+
67
+ | Rule | `recommended` | `vue` | `react` |
68
+ | --- | :---: | :---: | :---: |
69
+ | `module-structure` | error | error | error |
70
+ | `no-module-barrel` | error | error | error |
71
+ | `module-import-boundary` | error | error | error |
72
+ | `pure-ui-layer` | error | error | error |
73
+ | `module-index-required` | error | error | error |
74
+ | `no-t-for-variable` | error | error | error |
75
+ | `not-use-store-to-refs` | — | error | — |
76
+ | `not-use-custom-color-class` | — | warn | — |
77
+ | `no-export-type-from-component` | — | error | — |
78
+ | `no-export-type-from-tsx` | — | — | error |
79
+ | `no-import-from-package-path` | — | — | error |
80
+ | `no-multiple-component-exports` | — | — | error |
81
+
82
+ `vue` and `react` both extend `recommended`. Vue configs require `vue-eslint-parser`.
83
+
84
+ `no-export-type-from-component` and `no-export-type-from-tsx` enforce the same thing (no `export type`/`export interface` from a component file) — the former also matches `.vue` files, the latter is React-only. They're kept separate so each config only reports once per file.
package/dist/index.d.ts CHANGED
@@ -5,47 +5,51 @@ export declare const rules: {
5
5
  'pure-ui-layer': import("eslint").Rule.RuleModule;
6
6
  'module-index-required': import("eslint").Rule.RuleModule;
7
7
  'no-t-for-variable': import("eslint").Rule.RuleModule;
8
+ 'no-export-type-from-component': import("eslint").Rule.RuleModule;
8
9
  'not-use-store-to-refs': import("eslint").Rule.RuleModule;
9
10
  'not-use-custom-color-class': import("eslint").Rule.RuleModule;
10
11
  'no-export-type-from-tsx': import("eslint").Rule.RuleModule;
11
12
  'no-import-from-package-path': import("eslint").Rule.RuleModule;
13
+ 'no-multiple-component-exports': import("eslint").Rule.RuleModule;
12
14
  };
13
15
  export declare const configs: {
14
16
  recommended: {
15
- readonly plugins: readonly ['gem-fe'];
17
+ readonly plugins: readonly ['@gem-sdk'];
16
18
  readonly rules: {
17
- readonly 'gem-fe/module-structure': 'error';
18
- readonly 'gem-fe/no-module-barrel': 'error';
19
- readonly 'gem-fe/module-import-boundary': 'error';
20
- readonly 'gem-fe/pure-ui-layer': 'error';
21
- readonly 'gem-fe/module-index-required': 'error';
22
- readonly 'gem-fe/no-t-for-variable': 'error';
19
+ readonly '@gem-sdk/module-structure': 'error';
20
+ readonly '@gem-sdk/no-module-barrel': 'error';
21
+ readonly '@gem-sdk/module-import-boundary': 'error';
22
+ readonly '@gem-sdk/pure-ui-layer': 'error';
23
+ readonly '@gem-sdk/module-index-required': 'error';
24
+ readonly '@gem-sdk/no-t-for-variable': 'error';
23
25
  };
24
26
  };
25
27
  vue: {
26
- readonly plugins: readonly ['gem-fe'];
28
+ readonly plugins: readonly ['@gem-sdk'];
27
29
  readonly rules: {
28
- readonly 'gem-fe/module-structure': 'error';
29
- readonly 'gem-fe/no-module-barrel': 'error';
30
- readonly 'gem-fe/module-import-boundary': 'error';
31
- readonly 'gem-fe/pure-ui-layer': 'error';
32
- readonly 'gem-fe/module-index-required': 'error';
33
- readonly 'gem-fe/no-t-for-variable': 'error';
34
- readonly 'gem-fe/not-use-store-to-refs': 'error';
35
- readonly 'gem-fe/not-use-custom-color-class': 'warn';
30
+ readonly '@gem-sdk/module-structure': 'error';
31
+ readonly '@gem-sdk/no-module-barrel': 'error';
32
+ readonly '@gem-sdk/module-import-boundary': 'error';
33
+ readonly '@gem-sdk/pure-ui-layer': 'error';
34
+ readonly '@gem-sdk/module-index-required': 'error';
35
+ readonly '@gem-sdk/no-t-for-variable': 'error';
36
+ readonly '@gem-sdk/not-use-store-to-refs': 'error';
37
+ readonly '@gem-sdk/not-use-custom-color-class': 'warn';
38
+ readonly '@gem-sdk/no-export-type-from-component': 'error';
36
39
  };
37
40
  };
38
41
  react: {
39
- readonly plugins: readonly ['gem-fe'];
42
+ readonly plugins: readonly ['@gem-sdk'];
40
43
  readonly rules: {
41
- readonly 'gem-fe/module-structure': 'error';
42
- readonly 'gem-fe/no-module-barrel': 'error';
43
- readonly 'gem-fe/module-import-boundary': 'error';
44
- readonly 'gem-fe/pure-ui-layer': 'error';
45
- readonly 'gem-fe/module-index-required': 'error';
46
- readonly 'gem-fe/no-t-for-variable': 'error';
47
- readonly 'gem-fe/no-export-type-from-tsx': 'error';
48
- readonly 'gem-fe/no-import-from-package-path': 'error';
44
+ readonly '@gem-sdk/module-structure': 'error';
45
+ readonly '@gem-sdk/no-module-barrel': 'error';
46
+ readonly '@gem-sdk/module-import-boundary': 'error';
47
+ readonly '@gem-sdk/pure-ui-layer': 'error';
48
+ readonly '@gem-sdk/module-index-required': 'error';
49
+ readonly '@gem-sdk/no-t-for-variable': 'error';
50
+ readonly '@gem-sdk/no-export-type-from-tsx': 'error';
51
+ readonly '@gem-sdk/no-import-from-package-path': 'error';
52
+ readonly '@gem-sdk/no-multiple-component-exports': 'error';
49
53
  };
50
54
  };
51
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,KAAK;;;;;;;;;;;CAegB,CAAC;AAyCnC,eAAO,MAAM,OAAO;;oCAnCR,QAAQ;;qBAEhB,yBAAyB,EAAE,OAAO;qBAClC,yBAAyB,EAAE,OAAO;qBAClC,+BAA+B,EAAE,OAAO;qBACxC,sBAAsB,EAAE,OAAO;qBAC/B,8BAA8B,EAAE,OAAO;qBACvC,0BAA0B,EAAE,OAAO;;;;oCAQ3B,QAAQ;;gDAbW,OAAO;gDACP,OAAO;sDACD,OAAO;6CAChB,OAAO;qDACC,OAAO;iDACX,OAAO;qDAWH,OAAO;0DACF,MAAM;;;;oCAQnC,QAAQ;;gDAzBW,OAAO;gDACP,OAAO;sDACD,OAAO;6CAChB,OAAO;qDACC,OAAO;iDACX,OAAO;uDAuBD,OAAO;2DACH,OAAO;;;CAIC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,KAAK;;;;;;;;;;;;;CAkBgB,CAAC;AA2CnC,eAAO,MAAM,OAAO;;oCArCR,UAAU;;qBAElB,2BAA2B,EAAE,OAAO;qBACpC,2BAA2B,EAAE,OAAO;qBACpC,iCAAiC,EAAE,OAAO;qBAC1C,wBAAwB,EAAE,OAAO;qBACjC,gCAAgC,EAAE,OAAO;qBACzC,4BAA4B,EAAE,OAAO;;;;oCAQ7B,UAAU;;kDAbW,OAAO;kDACP,OAAO;wDACD,OAAO;+CAChB,OAAO;uDACC,OAAO;mDACX,OAAO;uDAWH,OAAO;4DACF,MAAM;+DACH,OAAO;;;;oCAQzC,UAAU;;kDA1BW,OAAO;kDACP,OAAO;wDACD,OAAO;+CAChB,OAAO;uDACC,OAAO;mDACX,OAAO;yDAwBD,OAAO;6DACH,OAAO;+DACL,OAAO;;;CAIH,CAAC"}
package/dist/index.js CHANGED
@@ -7,9 +7,11 @@ exports.configs = exports.rules = void 0;
7
7
  const moduleImportBoundary_1 = __importDefault(require("./rules/moduleImportBoundary"));
8
8
  const moduleIndexRequired_1 = __importDefault(require("./rules/moduleIndexRequired"));
9
9
  const moduleStructure_1 = __importDefault(require("./rules/moduleStructure"));
10
+ const noExportTypeFromComponent_1 = __importDefault(require("./rules/noExportTypeFromComponent"));
10
11
  const noExportTypeFromTsx_1 = __importDefault(require("./rules/noExportTypeFromTsx"));
11
12
  const noImportFromPackagePath_1 = __importDefault(require("./rules/noImportFromPackagePath"));
12
13
  const noModuleBarrel_1 = __importDefault(require("./rules/noModuleBarrel"));
14
+ const noMultipleComponentExports_1 = __importDefault(require("./rules/noMultipleComponentExports"));
13
15
  const noTForVariable_1 = __importDefault(require("./rules/noTForVariable"));
14
16
  const notUseCustomColorClass_1 = __importDefault(require("./rules/notUseCustomColorClass"));
15
17
  const notUseStoreToRefs_1 = __importDefault(require("./rules/notUseStoreToRefs"));
@@ -23,47 +25,52 @@ exports.rules = {
23
25
  'module-index-required': moduleIndexRequired_1.default,
24
26
  // i18n
25
27
  'no-t-for-variable': noTForVariable_1.default,
28
+ // Shared component rules — target both .tsx and .vue
29
+ 'no-export-type-from-component': noExportTypeFromComponent_1.default,
26
30
  // Vue-specific
27
31
  'not-use-store-to-refs': notUseStoreToRefs_1.default,
28
32
  'not-use-custom-color-class': notUseCustomColorClass_1.default,
29
33
  // React/TypeScript-specific
30
34
  'no-export-type-from-tsx': noExportTypeFromTsx_1.default,
31
35
  'no-import-from-package-path': noImportFromPackagePath_1.default,
36
+ 'no-multiple-component-exports': noMultipleComponentExports_1.default,
32
37
  };
33
38
  /**
34
39
  * Shared architecture + i18n rules for both React and Vue repos.
35
40
  */
36
41
  const recommended = {
37
- plugins: ['gem-fe'],
42
+ plugins: ['@gem-sdk'],
38
43
  rules: {
39
- 'gem-fe/module-structure': 'error',
40
- 'gem-fe/no-module-barrel': 'error',
41
- 'gem-fe/module-import-boundary': 'error',
42
- 'gem-fe/pure-ui-layer': 'error',
43
- 'gem-fe/module-index-required': 'error',
44
- 'gem-fe/no-t-for-variable': 'error',
44
+ '@gem-sdk/module-structure': 'error',
45
+ '@gem-sdk/no-module-barrel': 'error',
46
+ '@gem-sdk/module-import-boundary': 'error',
47
+ '@gem-sdk/pure-ui-layer': 'error',
48
+ '@gem-sdk/module-index-required': 'error',
49
+ '@gem-sdk/no-t-for-variable': 'error',
45
50
  },
46
51
  };
47
52
  /**
48
53
  * `recommended` + Vue-specific rules. Use with vue-eslint-parser.
49
54
  */
50
55
  const vue = {
51
- plugins: ['gem-fe'],
56
+ plugins: ['@gem-sdk'],
52
57
  rules: {
53
58
  ...recommended.rules,
54
- 'gem-fe/not-use-store-to-refs': 'error',
55
- 'gem-fe/not-use-custom-color-class': 'warn',
59
+ '@gem-sdk/not-use-store-to-refs': 'error',
60
+ '@gem-sdk/not-use-custom-color-class': 'warn',
61
+ '@gem-sdk/no-export-type-from-component': 'error',
56
62
  },
57
63
  };
58
64
  /**
59
65
  * `recommended` + React/TypeScript-specific rules.
60
66
  */
61
67
  const react = {
62
- plugins: ['gem-fe'],
68
+ plugins: ['@gem-sdk'],
63
69
  rules: {
64
70
  ...recommended.rules,
65
- 'gem-fe/no-export-type-from-tsx': 'error',
66
- 'gem-fe/no-import-from-package-path': 'error',
71
+ '@gem-sdk/no-export-type-from-tsx': 'error',
72
+ '@gem-sdk/no-import-from-package-path': 'error',
73
+ '@gem-sdk/no-multiple-component-exports': 'error',
67
74
  },
68
75
  };
69
76
  exports.configs = { recommended, vue, react };
@@ -0,0 +1,8 @@
1
+ import type { Rule } from 'eslint';
2
+ /**
3
+ * Disallows exporting types and interfaces from component files (.tsx and .vue).
4
+ * Types should live in a dedicated .ts file.
5
+ */
6
+ declare const rule: Rule.RuleModule;
7
+ export default rule;
8
+ //# sourceMappingURL=noExportTypeFromComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noExportTypeFromComponent.d.ts","sourceRoot":"","sources":["../../src/rules/noExportTypeFromComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAInC;;;GAGG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAwBhB,CAAC;eAEa,IAAI"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const COMPONENT_EXTENSIONS = ['.tsx', '.vue'];
4
+ /**
5
+ * Disallows exporting types and interfaces from component files (.tsx and .vue).
6
+ * Types should live in a dedicated .ts file.
7
+ */
8
+ const rule = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Disallow export type and interface declarations from .tsx and .vue files',
13
+ },
14
+ messages: {
15
+ noTypeExport: 'Exporting types and interfaces from .tsx and .vue files is not allowed. Move them to a .ts file.',
16
+ },
17
+ schema: [],
18
+ },
19
+ create(context) {
20
+ return {
21
+ ExportNamedDeclaration(node) {
22
+ const fileName = context.getFilename();
23
+ if (COMPONENT_EXTENSIONS.some((ext) => fileName.endsWith(ext)) &&
24
+ node.exportKind === 'type') {
25
+ context.report({ node, messageId: 'noTypeExport' });
26
+ }
27
+ },
28
+ };
29
+ },
30
+ };
31
+ exports.default = rule;
@@ -0,0 +1,4 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=noMultipleComponentExports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noMultipleComponentExports.d.ts","sourceRoot":"","sources":["../../src/rules/noMultipleComponentExports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AA8EnC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA+BhB,CAAC;eAEa,IAAI"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PASCAL_CASE = /^[A-Z][A-Za-z0-9]*$/;
4
+ function isComponentInit(node) {
5
+ return !!node && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression');
6
+ }
7
+ function findLocalComponent(body, name) {
8
+ for (const statement of body) {
9
+ if (statement.type === 'VariableDeclaration') {
10
+ for (const declarator of statement.declarations) {
11
+ if (declarator.id.type === 'Identifier' && declarator.id.name === name && isComponentInit(declarator.init)) {
12
+ return { name, node: declarator };
13
+ }
14
+ }
15
+ }
16
+ if (statement.type === 'FunctionDeclaration' && statement.id?.name === name) {
17
+ return { name, node: statement };
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+ /**
23
+ * Detects components exported at the top level of a file, covering `export const X = () => {}`,
24
+ * `export function X() {}`, `export default`, and `export { X }` re-exports of local declarations.
25
+ */
26
+ function collectExportedComponents(program) {
27
+ const components = [];
28
+ for (const statement of program.body) {
29
+ if (statement.type === 'ExportNamedDeclaration') {
30
+ const declaration = statement.declaration;
31
+ if (declaration?.type === 'VariableDeclaration') {
32
+ for (const declarator of declaration.declarations) {
33
+ if (declarator.id.type === 'Identifier' &&
34
+ PASCAL_CASE.test(declarator.id.name) &&
35
+ isComponentInit(declarator.init)) {
36
+ components.push({ name: declarator.id.name, node: declarator });
37
+ }
38
+ }
39
+ }
40
+ else if (declaration?.type === 'FunctionDeclaration' && declaration.id && PASCAL_CASE.test(declaration.id.name)) {
41
+ components.push({ name: declaration.id.name, node: declaration });
42
+ }
43
+ for (const specifier of statement.specifiers) {
44
+ if (specifier.exported.type !== 'Identifier' || specifier.local.type !== 'Identifier')
45
+ continue;
46
+ const exportedName = specifier.exported.name;
47
+ if (!PASCAL_CASE.test(exportedName))
48
+ continue;
49
+ const local = findLocalComponent(program.body, specifier.local.name);
50
+ if (local)
51
+ components.push({ name: exportedName, node: specifier });
52
+ }
53
+ }
54
+ if (statement.type === 'ExportDefaultDeclaration') {
55
+ const declaration = statement.declaration;
56
+ if (declaration.type === 'FunctionDeclaration' || declaration.type === 'ArrowFunctionExpression' || declaration.type === 'FunctionExpression') {
57
+ const name = declaration.type === 'FunctionDeclaration' ? declaration.id?.name : undefined;
58
+ if (!name || PASCAL_CASE.test(name)) {
59
+ components.push({ name: name ?? 'default', node: declaration });
60
+ }
61
+ }
62
+ else if (declaration.type === 'Identifier') {
63
+ const local = findLocalComponent(program.body, declaration.name);
64
+ if (local)
65
+ components.push(local);
66
+ }
67
+ }
68
+ }
69
+ return components;
70
+ }
71
+ const rule = {
72
+ meta: {
73
+ type: 'problem',
74
+ docs: {
75
+ description: 'Disallow defining and exporting more than one component from a single file',
76
+ },
77
+ messages: {
78
+ multipleComponents: 'Only one component should be defined and exported per file. "{{name}}" is an additional exported component — move it to its own file.',
79
+ },
80
+ schema: [],
81
+ },
82
+ create(context) {
83
+ return {
84
+ Program(node) {
85
+ const filename = context.getFilename();
86
+ if (!filename.endsWith('.tsx'))
87
+ return;
88
+ const components = collectExportedComponents(node);
89
+ if (components.length <= 1)
90
+ return;
91
+ for (const component of components.slice(1)) {
92
+ context.report({
93
+ node: component.node,
94
+ messageId: 'multipleComponents',
95
+ data: { name: component.name },
96
+ });
97
+ }
98
+ },
99
+ };
100
+ },
101
+ };
102
+ exports.default = rule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gem-sdk/eslint-plugin",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Shared ESLint rules for GemPages frontend repos (React + Vue)",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -13,8 +13,8 @@
13
13
  "test": "vitest run",
14
14
  "type-check": "tsc --noEmit",
15
15
  "lint": "eslint src --ext .ts",
16
- "release": "changeset publish",
17
- "release-packages": "yarn build && changeset publish"
16
+ "release": "set -a && . ./.env && set +a && env npm_config_//registry.npmjs.org/:_authToken=\"$NPM_TOKEN\" changeset publish",
17
+ "release-packages": "set -a && . ./.env && set +a && yarn build && env npm_config_//registry.npmjs.org/:_authToken=\"$NPM_TOKEN\" changeset publish"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "eslint": ">=8.0.0"