Skip to content

Commit 6f21d2f

Browse files
committed
fix gts type aware by emulating them to ts files
set our own ts.sys with ts.setSys typescript-eslint has handling for a lot of scenarios for file changes and project changes etc use mts extension to keep same offsets
1 parent a08f0ab commit 6f21d2f

File tree

10 files changed

+768
-519
lines changed

10 files changed

+768
-519
lines changed

lib/parsers/gjs-gts-parser.js

Lines changed: 9 additions & 516 deletions
Large diffs are not rendered by default.

lib/parsers/transform.js

Lines changed: 519 additions & 0 deletions
Large diffs are not rendered by default.

lib/parsers/ts-utils.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const fs = require('node:fs');
2+
const ts = require('typescript');
3+
const { transformForLint } = require('./transform');
4+
const babel = require('@babel/core');
5+
const { replaceRange } = require('./transform');
6+
7+
module.exports.patchTs = function patchTs() {
8+
const sys = { ...ts.sys };
9+
const newSys = {
10+
...ts.sys,
11+
readDirectory(...args) {
12+
const results = sys.readDirectory.call(this, ...args);
13+
return [
14+
...results,
15+
...results.filter((x) => x.endsWith('.gts')).map((f) => f.replace(/\.gts$/, '.mts')),
16+
];
17+
},
18+
fileExists(fileName) {
19+
return fs.existsSync(fileName.replace(/\.mts$/, '.gts')) || fs.existsSync(fileName);
20+
},
21+
readFile(fname) {
22+
let fileName = fname;
23+
let content = '';
24+
try {
25+
content = fs.readFileSync(fileName).toString();
26+
} catch {
27+
fileName = fileName.replace(/\.mts$/, '.gts');
28+
content = fs.readFileSync(fileName).toString();
29+
}
30+
if (fileName.endsWith('.gts')) {
31+
content = transformForLint(content).output;
32+
}
33+
if ((!fileName.endsWith('.d.ts') && fileName.endsWith('.ts')) || fileName.endsWith('.gts')) {
34+
content = replaceExtensions(content);
35+
}
36+
return content;
37+
},
38+
};
39+
ts.setSys(newSys);
40+
};
41+
42+
function replaceExtensions(code) {
43+
let jsCode = code;
44+
const babelParseResult = babel.parse(jsCode, {
45+
parserOpts: { ranges: true, plugins: ['typescript'] },
46+
});
47+
const length = jsCode.length;
48+
for (const b of babelParseResult.program.body) {
49+
if (b.type === 'ImportDeclaration' && b.source.value.endsWith('.gts')) {
50+
const value = b.source.value.replace(/\.gts$/, '.mts');
51+
const strWrapper = jsCode[b.source.start];
52+
jsCode = replaceRange(jsCode, b.source.start, b.source.end, strWrapper + value + strWrapper);
53+
}
54+
}
55+
if (length !== jsCode.length) {
56+
throw new Error('bad replacement');
57+
}
58+
return jsCode;
59+
}
60+
61+
module.exports.replaceExtensions = replaceExtensions;

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
]
6969
},
7070
"dependencies": {
71+
"@babel/core": "^7.23.3",
7172
"@babel/eslint-parser": "^7.22.15",
7273
"@ember-data/rfc395-data": "^0.0.4",
7374
"@glimmer/syntax": "^0.85.12",
@@ -116,7 +117,13 @@
116117
"typescript": "^5.2.2"
117118
},
118119
"peerDependencies": {
119-
"eslint": ">= 8"
120+
"eslint": ">= 8",
121+
"typescript": "*"
122+
},
123+
"peerDependenciesMeta": {
124+
"typescript": {
125+
"optional": true
126+
}
120127
},
121128
"engines": {
122129
"node": "18.* || 20.* || >= 21"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const fortyTwoFromGTS = '42';
2+
3+
<template>
4+
{{fortyTwoFromGTS}}
5+
</template>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const fortyTwoFromTS = '42';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { fortyTwoFromGTS } from './bar.gts';
2+
import { fortyTwoFromTS } from './baz.ts';
3+
4+
export const fortyTwoLocal = '42';
5+
6+
const helloWorldFromTS = fortyTwoFromTS[0] === '4' ? 'hello' : 'world';
7+
const helloWorldFromGTS = fortyTwoFromGTS[0] === '4' ? 'hello' : 'world';
8+
const helloWorld = fortyTwoLocal[0] === '4' ? 'hello' : 'world';
9+
//
10+
<template>
11+
{{helloWorldFromGTS}}
12+
{{helloWorldFromTS}}
13+
{{helloWorld}}
14+
</template>

tests/lib/rules-preprocessor/gjs-gts-parser-test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,4 +765,80 @@ describe('multiple tokens in same file', () => {
765765
expect(resultErrors[2].message).toBe("'bar' is not defined.");
766766
expect(resultErrors[2].line).toBe(17);
767767
});
768+
769+
it('lints while being type aware', async () => {
770+
const eslint = new ESLint({
771+
ignore: false,
772+
useEslintrc: false,
773+
plugins: { ember: plugin },
774+
overrideConfig: {
775+
root: true,
776+
env: {
777+
browser: true,
778+
},
779+
plugins: ['ember'],
780+
extends: ['plugin:ember/recommended'],
781+
overrides: [
782+
{
783+
files: ['**/*.gts'],
784+
parser: 'eslint-plugin-ember/gjs-gts-parser',
785+
parserOptions: {
786+
project: './tsconfig.eslint.json',
787+
tsconfigRootDir: __dirname,
788+
extraFileExtensions: ['.gts'],
789+
},
790+
extends: [
791+
'plugin:@typescript-eslint/recommended-requiring-type-checking',
792+
'plugin:ember/recommended',
793+
],
794+
rules: {
795+
'no-trailing-spaces': 'error',
796+
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
797+
},
798+
},
799+
{
800+
files: ['**/*.ts'],
801+
parser: '@typescript-eslint/parser',
802+
parserOptions: {
803+
project: './tsconfig.eslint.json',
804+
tsconfigRootDir: __dirname,
805+
extraFileExtensions: ['.gts'],
806+
},
807+
extends: [
808+
'plugin:@typescript-eslint/recommended-requiring-type-checking',
809+
'plugin:ember/recommended',
810+
],
811+
rules: {
812+
'no-trailing-spaces': 'error',
813+
},
814+
},
815+
],
816+
rules: {
817+
quotes: ['error', 'single'],
818+
semi: ['error', 'always'],
819+
'object-curly-spacing': ['error', 'always'],
820+
'lines-between-class-members': 'error',
821+
'no-undef': 'error',
822+
'no-unused-vars': 'error',
823+
'ember/no-get': 'off',
824+
'ember/no-array-prototype-extensions': 'error',
825+
'ember/no-unused-services': 'error',
826+
},
827+
},
828+
});
829+
830+
const results = await eslint.lintFiles(['**/*.gts', '**/*.ts']);
831+
832+
const resultErrors = results.flatMap((result) => result.messages);
833+
expect(resultErrors).toHaveLength(3);
834+
835+
expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead.");
836+
expect(resultErrors[0].line).toBe(6);
837+
838+
expect(resultErrors[1].line).toBe(7);
839+
expect(resultErrors[1].message).toBe("Use 'String#startsWith' method instead.");
840+
841+
expect(resultErrors[2].line).toBe(8);
842+
expect(resultErrors[2].message).toBe("Use 'String#startsWith' method instead.");
843+
});
768844
});

tests/lib/rules-preprocessor/tsconfig.eslint.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"strictNullChecks": true
66
},
77
"include": [
8-
"*"
9-
]
8+
"**/*.ts",
9+
"**/*.gts"
10+
],
1011
}

yarn.lock

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@
4949
json5 "^2.2.3"
5050
semver "^6.3.1"
5151

52+
"@babel/core@^7.23.3":
53+
version "7.23.3"
54+
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.3.tgz#5ec09c8803b91f51cc887dedc2654a35852849c9"
55+
integrity sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==
56+
dependencies:
57+
"@ampproject/remapping" "^2.2.0"
58+
"@babel/code-frame" "^7.22.13"
59+
"@babel/generator" "^7.23.3"
60+
"@babel/helper-compilation-targets" "^7.22.15"
61+
"@babel/helper-module-transforms" "^7.23.3"
62+
"@babel/helpers" "^7.23.2"
63+
"@babel/parser" "^7.23.3"
64+
"@babel/template" "^7.22.15"
65+
"@babel/traverse" "^7.23.3"
66+
"@babel/types" "^7.23.3"
67+
convert-source-map "^2.0.0"
68+
debug "^4.1.0"
69+
gensync "^1.0.0-beta.2"
70+
json5 "^2.2.3"
71+
semver "^6.3.1"
72+
5273
"@babel/eslint-parser@^7.22.15":
5374
version "7.22.15"
5475
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.22.15.tgz#263f059c476e29ca4972481a17b8b660cb025a34"
@@ -68,6 +89,16 @@
6889
"@jridgewell/trace-mapping" "^0.3.17"
6990
jsesc "^2.5.1"
7091

92+
"@babel/generator@^7.23.3":
93+
version "7.23.3"
94+
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.3.tgz#86e6e83d95903fbe7613f448613b8b319f330a8e"
95+
integrity sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==
96+
dependencies:
97+
"@babel/types" "^7.23.3"
98+
"@jridgewell/gen-mapping" "^0.3.2"
99+
"@jridgewell/trace-mapping" "^0.3.17"
100+
jsesc "^2.5.1"
101+
71102
"@babel/helper-annotate-as-pure@^7.22.5":
72103
version "7.22.5"
73104
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
@@ -146,6 +177,17 @@
146177
"@babel/helper-split-export-declaration" "^7.22.6"
147178
"@babel/helper-validator-identifier" "^7.22.20"
148179

180+
"@babel/helper-module-transforms@^7.23.3":
181+
version "7.23.3"
182+
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1"
183+
integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==
184+
dependencies:
185+
"@babel/helper-environment-visitor" "^7.22.20"
186+
"@babel/helper-module-imports" "^7.22.15"
187+
"@babel/helper-simple-access" "^7.22.5"
188+
"@babel/helper-split-export-declaration" "^7.22.6"
189+
"@babel/helper-validator-identifier" "^7.22.20"
190+
149191
"@babel/helper-optimise-call-expression@^7.22.5":
150192
version "7.22.5"
151193
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
@@ -226,6 +268,11 @@
226268
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
227269
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
228270

271+
"@babel/parser@^7.23.3":
272+
version "7.23.3"
273+
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9"
274+
integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==
275+
229276
"@babel/plugin-proposal-class-properties@^7.18.6":
230277
version "7.18.6"
231278
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
@@ -375,6 +422,22 @@
375422
debug "^4.1.0"
376423
globals "^11.1.0"
377424

425+
"@babel/traverse@^7.23.3":
426+
version "7.23.3"
427+
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b"
428+
integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==
429+
dependencies:
430+
"@babel/code-frame" "^7.22.13"
431+
"@babel/generator" "^7.23.3"
432+
"@babel/helper-environment-visitor" "^7.22.20"
433+
"@babel/helper-function-name" "^7.23.0"
434+
"@babel/helper-hoist-variables" "^7.22.5"
435+
"@babel/helper-split-export-declaration" "^7.22.6"
436+
"@babel/parser" "^7.23.3"
437+
"@babel/types" "^7.23.3"
438+
debug "^4.1.0"
439+
globals "^11.1.0"
440+
378441
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3":
379442
version "7.23.0"
380443
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
@@ -384,6 +447,15 @@
384447
"@babel/helper-validator-identifier" "^7.22.20"
385448
to-fast-properties "^2.0.0"
386449

450+
"@babel/types@^7.23.3":
451+
version "7.23.3"
452+
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.3.tgz#d5ea892c07f2ec371ac704420f4dcdb07b5f9598"
453+
integrity sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==
454+
dependencies:
455+
"@babel/helper-string-parser" "^7.22.5"
456+
"@babel/helper-validator-identifier" "^7.22.20"
457+
to-fast-properties "^2.0.0"
458+
387459
"@bcoe/v8-coverage@^0.2.3":
388460
version "0.2.3"
389461
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"

0 commit comments

Comments
 (0)