Skip to content

Commit 87fcd5d

Browse files
committed
add eslint rule for platform export
1 parent 965b073 commit 87fcd5d

File tree

8 files changed

+469
-0
lines changed

8 files changed

+469
-0
lines changed

.eslintrc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module.exports = {
88
'eslint:recommended',
99
'plugin:@typescript-eslint/recommended',
1010
],
11+
parser: '@typescript-eslint/parser',
12+
plugins: ['@typescript-eslint', 'local-rules'],
1113
globals: {
1214
Atomics: 'readonly',
1315
SharedArrayBuffer: 'readonly',
@@ -25,6 +27,13 @@ module.exports = {
2527
'rules': {
2628
'@typescript-eslint/explicit-module-boundary-types': ['error']
2729
}
30+
},
31+
{
32+
'files': ['*.ts', '!*.spec.ts', '!*.test.ts', '!*.tests.ts', '!*.test-d.ts'],
33+
'excludedFiles': ['**/__mocks__/**', '**/tests/**'],
34+
'rules': {
35+
'local-rules/require-platform-declaration': 'error',
36+
}
2837
}
2938
],
3039
rules: {

ESLINT_TROUBLESHOOTING.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# ESLint Rule Troubleshooting
2+
3+
## The Rule is Working!
4+
5+
The `require-platform-declaration` rule **is** working correctly from the command line:
6+
7+
```bash
8+
$ npx eslint lib/core/custom_attribute_condition_evaluator/index.ts
9+
10+
lib/core/custom_attribute_condition_evaluator/index.ts
11+
16:1 error File must export __supportedPlatforms to declare which platforms
12+
it supports. Example: export const __supportedPlatforms = ['__universal__'] as const;
13+
```
14+
15+
## VSCode Not Showing Errors?
16+
17+
If VSCode isn't showing the ESLint errors, try these steps:
18+
19+
### 1. Restart ESLint Server
20+
- Open Command Palette: `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux)
21+
- Type: `ESLint: Restart ESLint Server`
22+
- Press Enter
23+
24+
### 2. Check ESLint Extension is Installed
25+
- Open Extensions panel: `Cmd+Shift+X` (Mac) or `Ctrl+Shift+X` (Windows/Linux)
26+
- Search for "ESLint" by Microsoft
27+
- Make sure it's installed and enabled
28+
29+
### 3. Check ESLint Output
30+
- Open Output panel: `Cmd+Shift+U` (Mac) or `Ctrl+Shift+U` (Windows/Linux)
31+
- Select "ESLint" from the dropdown
32+
- Look for any error messages
33+
34+
### 4. Reload VSCode Window
35+
- Open Command Palette: `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux)
36+
- Type: `Developer: Reload Window`
37+
- Press Enter
38+
39+
### 5. Check File is Being Linted
40+
The rule only applies to:
41+
- ✅ Files in `lib/` or `src/` directory
42+
- ✅ TypeScript files (`.ts`)
43+
- ❌ Test files (`.spec.ts`, `.test.ts`, etc.)
44+
- ❌ Declaration files (`.d.ts`)
45+
46+
### 6. Verify ESLint Configuration
47+
Check that `.eslintrc.js` has the parser set:
48+
```javascript
49+
parser: '@typescript-eslint/parser',
50+
```
51+
52+
And that the rule is in the overrides:
53+
```javascript
54+
overrides: [{
55+
files: ['*.ts', '!*.spec.ts', '!*.test.ts', '!*.tests.ts', '!*.test-d.ts'],
56+
rules: {
57+
'local-rules/require-platform-declaration': 'error',
58+
}
59+
}]
60+
```
61+
62+
## Manual Verification
63+
64+
You can always verify the rule works by running:
65+
66+
```bash
67+
# Check a specific file
68+
npx eslint lib/service.ts
69+
70+
# Check all lib files (shows only errors)
71+
npx eslint lib/**/*.ts --quiet
72+
```
73+
74+
## Adding __supportedPlatforms
75+
76+
To fix the error, add this export to your file (after imports):
77+
78+
```typescript
79+
// Universal file (all platforms)
80+
export const __supportedPlatforms = ['__universal__'] as const;
81+
82+
// OR platform-specific file
83+
export const __supportedPlatforms = ['browser', 'node'] as const;
84+
```

eslint-local-rules/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Local ESLint Rules
2+
3+
This directory contains custom ESLint rules specific to this project.
4+
5+
## Rules
6+
7+
### `require-platform-declaration`
8+
9+
**Purpose:** Ensures all source files (except tests) export `__supportedPlatforms` to declare which platforms they support.
10+
11+
**Why:** This enforces platform isolation at the linting level, catching missing declarations before build time.
12+
13+
**Enforcement:**
14+
- ✅ Enabled for all `.ts` files in `lib/` directory
15+
- ❌ Disabled for test files (`.spec.ts`, `.test.ts`, etc.)
16+
- ❌ Disabled for `__mocks__` and `tests` directories
17+
18+
**Valid Examples:**
19+
20+
```typescript
21+
// Universal file (all platforms)
22+
export const __supportedPlatforms = ['__universal__'] as const;
23+
24+
// Platform-specific file
25+
export const __supportedPlatforms = ['browser', 'node'] as const;
26+
27+
// With type annotation
28+
export const __supportedPlatforms: Platform[] = ['react_native'] as const;
29+
```
30+
31+
**Invalid:**
32+
33+
```typescript
34+
// Missing __supportedPlatforms export
35+
// ESLint Error: File must export __supportedPlatforms to declare which platforms it supports
36+
```
37+
38+
## Configuration
39+
40+
The rules are loaded via `eslint-plugin-local-rules` and configured in `.eslintrc.js`:
41+
42+
```javascript
43+
{
44+
plugins: ['local-rules'],
45+
overrides: [{
46+
files: ['*.ts', '!*.spec.ts', '!*.test.ts'],
47+
rules: {
48+
'local-rules/require-platform-declaration': 'error'
49+
}
50+
}]
51+
}
52+
```
53+
54+
## Adding New Rules
55+
56+
1. Create a new rule file in this directory (e.g., `my-rule.js`)
57+
2. Export the rule following ESLint's rule format
58+
3. Add it to `index.js`:
59+
```javascript
60+
module.exports = {
61+
'require-platform-declaration': require('./require-platform-declaration'),
62+
'my-rule': require('./my-rule'), // Add here
63+
};
64+
```
65+
4. Enable it in `.eslintrc.js`
66+
67+
## Testing Rules
68+
69+
Run ESLint on specific files to test:
70+
71+
```bash
72+
# Test on a specific file
73+
npx eslint lib/service.ts
74+
75+
# Test on all lib files
76+
npx eslint lib/**/*.ts --quiet
77+
```

eslint-local-rules/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Local ESLint Rules
3+
*
4+
* Custom ESLint rules for the project.
5+
* Loaded by eslint-plugin-local-rules.
6+
*/
7+
8+
module.exports = {
9+
'require-platform-declaration': require('./require-platform-declaration'),
10+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* ESLint Rule: require-platform-declaration
3+
*
4+
* Ensures that all non-test source files export __supportedPlatforms
5+
*
6+
* Valid:
7+
* export const __supportedPlatforms = ['browser'] as const;
8+
* export const __supportedPlatforms = ['__universal__'] as const;
9+
* export const __supportedPlatforms: Platform[] = ['browser', 'node'];
10+
*
11+
* Invalid:
12+
* // Missing __supportedPlatforms export
13+
*/
14+
15+
module.exports = {
16+
meta: {
17+
type: 'problem',
18+
docs: {
19+
description: 'Require __supportedPlatforms export in all source files',
20+
category: 'Best Practices',
21+
recommended: true,
22+
},
23+
messages: {
24+
missingPlatformDeclaration: 'File must export __supportedPlatforms to declare which platforms it supports. Example: export const __supportedPlatforms = [\'__universal__\'] as const;',
25+
invalidPlatformDeclaration: '__supportedPlatforms must be exported as a const array. Example: export const __supportedPlatforms = [\'browser\', \'node\'] as const;',
26+
},
27+
schema: [],
28+
},
29+
30+
create(context) {
31+
const filename = context.getFilename();
32+
33+
// Skip test files
34+
if (filename.endsWith('.spec.ts') ||
35+
filename.endsWith('.test.ts') ||
36+
filename.endsWith('.tests.ts') ||
37+
filename.endsWith('.test.js') ||
38+
filename.endsWith('.spec.js') ||
39+
filename.endsWith('.tests.js') ||
40+
filename.endsWith('.test-d.ts') ||
41+
filename.endsWith('.d.ts') ||
42+
filename.includes('/__mocks__/') ||
43+
filename.includes('/tests/')) {
44+
return {};
45+
}
46+
47+
// Skip non-source files
48+
if (!filename.includes('/lib/') && !filename.includes('/src/')) {
49+
return {};
50+
}
51+
52+
let hasPlatformExport = false;
53+
let isValidExport = false;
54+
55+
return {
56+
ExportNamedDeclaration(node) {
57+
// Check for: export const __supportedPlatforms = [...]
58+
if (node.declaration &&
59+
node.declaration.type === 'VariableDeclaration') {
60+
61+
for (const declarator of node.declaration.declarations) {
62+
if (declarator.id.type === 'Identifier' &&
63+
declarator.id.name === '__supportedPlatforms') {
64+
65+
hasPlatformExport = true;
66+
67+
// Validate it's a const
68+
if (node.declaration.kind !== 'const') {
69+
context.report({
70+
node: declarator,
71+
messageId: 'invalidPlatformDeclaration',
72+
});
73+
return;
74+
}
75+
76+
// Validate it's an array
77+
let init = declarator.init;
78+
79+
// Handle TSAsExpression: [...] as const
80+
if (init && init.type === 'TSAsExpression') {
81+
init = init.expression;
82+
}
83+
84+
// Handle TSTypeAssertion: <const>[...]
85+
if (init && init.type === 'TSTypeAssertion') {
86+
init = init.expression;
87+
}
88+
89+
if (init && init.type === 'ArrayExpression') {
90+
isValidExport = true;
91+
} else {
92+
context.report({
93+
node: declarator,
94+
messageId: 'invalidPlatformDeclaration',
95+
});
96+
}
97+
}
98+
}
99+
}
100+
},
101+
102+
'Program:exit'(node) {
103+
// At the end of the file, check if __supportedPlatforms was exported
104+
if (!hasPlatformExport) {
105+
context.report({
106+
node,
107+
messageId: 'missingPlatformDeclaration',
108+
});
109+
}
110+
},
111+
};
112+
},
113+
};

lib/platform_support.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
*/
1111
export type Platform = 'browser' | 'node' | 'react_native' | '__universal__';
1212

13+
export const __supportedPlatforms = ['__universal__'] as const;
14+
1315
/**
1416
* Platform support declaration
1517
*

0 commit comments

Comments
 (0)