diff --git a/lib/parsers/gjs-gts-parser.js b/lib/parsers/gjs-gts-parser.js index e23be687d6..849a078e1f 100644 --- a/lib/parsers/gjs-gts-parser.js +++ b/lib/parsers/gjs-gts-parser.js @@ -27,10 +27,9 @@ module.exports = { const isTypescript = options.filePath.endsWith('.gts'); let result = null; - let filePath = options.filePath; + const filePath = options.filePath; if (options.project) { jsCode = replaceExtensions(jsCode); - filePath = options.filePath.replace(/\.gts$/, '.ts'); } result = isTypescript @@ -44,6 +43,19 @@ module.exports = { const visitorKeys = { ...result.visitorKeys, ...templateVisitorKeys }; result.isTypescript = isTypescript; convertAst(result, preprocessedResult, visitorKeys); + if (result.services?.program) { + const sourceFiles = result.services?.program.getSourceFiles(); + for (const sourceFile of sourceFiles) { + if (sourceFile.path.endsWith('.gts')) { + const mtsSourceFile = result.services?.program.getSourceFile( + sourceFile.path.replace(/\.gts$/, '.mts') + ); + if (mtsSourceFile) { + Object.assign(mtsSourceFile, sourceFile); + } + } + } + } return { ...result, visitorKeys }; }, }; diff --git a/lib/parsers/ts-utils.js b/lib/parsers/ts-utils.js index ef2d5761ac..5de91927c6 100644 --- a/lib/parsers/ts-utils.js +++ b/lib/parsers/ts-utils.js @@ -5,31 +5,38 @@ const babel = require('@babel/core'); const { replaceRange } = require('./transform'); module.exports.patchTs = function patchTs() { - const readDirectory = ts.sys.readDirectory; - ts.sys.readDirectory = function (...args) { - const results = readDirectory.call(this, ...args); - return results.map((f) => f.replace(/\.gts$/, '.ts')); - }; - ts.sys.fileExists = function (fileName) { - return fs.existsSync(fileName.replace(/\.mts$/, '.gts')) || fs.existsSync(fileName); - }; - ts.sys.readFile = function (fname) { - let fileName = fname; - let content = ''; - try { - content = fs.readFileSync(fileName).toString(); - } catch { - fileName = fileName.replace(/\.mts$/, '.gts'); - content = fs.readFileSync(fileName).toString(); - } - if (fileName.endsWith('.gts')) { - content = transformForLint(content).output; - } - if (fileName.endsWith('.ts') || fileName.endsWith('.gts')) { - content = replaceExtensions(content); - } - return content; + const sys = { ...ts.sys }; + const newSys = { + ...ts.sys, + readDirectory(...args) { + const results = sys.readDirectory.call(this, ...args); + return [ + ...results, + ...results.filter((x) => x.endsWith('.gts')).map((f) => f.replace(/\.gts$/, '.mts')), + ]; + }, + fileExists(fileName) { + return fs.existsSync(fileName.replace(/\.mts$/, '.gts')) || fs.existsSync(fileName); + }, + readFile(fname) { + let fileName = fname; + let content = ''; + try { + content = fs.readFileSync(fileName).toString(); + } catch { + fileName = fileName.replace(/\.mts$/, '.gts'); + content = fs.readFileSync(fileName).toString(); + } + if (fileName.endsWith('.gts')) { + content = transformForLint(content).output; + } + if ((!fileName.endsWith('.d.ts') && fileName.endsWith('.ts')) || fileName.endsWith('.gts')) { + content = replaceExtensions(content); + } + return content; + }, }; + ts.setSys(newSys); }; function replaceExtensions(code) { diff --git a/package.json b/package.json index e201afe148..01f037bcdc 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,7 @@ "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "requireindex": "^1.2.0", - "snake-case": "^3.0.3", - "typescript": "^5.2.2" + "snake-case": "^3.0.3" }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", @@ -114,7 +113,8 @@ "npm-run-all": "^4.1.5", "prettier": "^3.0.3", "release-it": "^16.2.1", - "sort-package-json": "^2.6.0" + "sort-package-json": "^2.6.0", + "typescript": "^5.2.2" }, "peerDependencies": { "eslint": ">= 8", diff --git a/tests/lib/rules-preprocessor/gjs-gts-parser-test.js b/tests/lib/rules-preprocessor/gjs-gts-parser-test.js index eb8624129f..a98e296bd7 100644 --- a/tests/lib/rules-preprocessor/gjs-gts-parser-test.js +++ b/tests/lib/rules-preprocessor/gjs-gts-parser-test.js @@ -9,6 +9,8 @@ const { ESLint } = require('eslint'); const plugin = require('../../../lib'); +const { writeFileSync, readFileSync } = require('node:fs'); +const { join } = require('node:path'); const gjsGtsParser = require.resolve('../../../lib/parsers/gjs-gts-parser'); @@ -388,18 +390,28 @@ const invalid = [ code: ` import Component from '@glimmer/component'; + const foo: any = ''; + export default class MyComponent extends Component { foo = 'bar'; }`, errors: [ + { + message: 'Unexpected any. Specify a different type.', + line: 4, + endLine: 4, + column: 18, + endColumn: 21, + }, { message: 'Trailing spaces not allowed.', - line: 8, - endLine: 8, + line: 10, + endLine: 10, column: 22, endColumn: 24, }, @@ -827,12 +839,12 @@ describe('multiple tokens in same file', () => { }, }); - const results = await eslint.lintFiles(['**/*.gts', '**/*.ts']); + let results = await eslint.lintFiles(['**/*.gts', '**/*.ts']); - const resultErrors = results.flatMap((result) => result.messages); + let resultErrors = results.flatMap((result) => result.messages); expect(resultErrors).toHaveLength(3); - expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead."); // Actual result is "Unsafe member access [0] on an `any` value." + expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead."); expect(resultErrors[0].line).toBe(6); expect(resultErrors[1].line).toBe(7); @@ -840,5 +852,38 @@ describe('multiple tokens in same file', () => { expect(resultErrors[2].line).toBe(8); expect(resultErrors[2].message).toBe("Use 'String#startsWith' method instead."); + + const filePath = join(__dirname, 'ember_ts', 'bar.gts'); + const content = readFileSync(filePath).toString(); + try { + writeFileSync(filePath, content.replace("'42'", '42')); + + results = await eslint.lintFiles(['**/*.gts', '**/*.ts']); + + resultErrors = results.flatMap((result) => result.messages); + expect(resultErrors).toHaveLength(2); + + expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead."); + expect(resultErrors[0].line).toBe(6); + + expect(resultErrors[1].line).toBe(8); + expect(resultErrors[1].message).toBe("Use 'String#startsWith' method instead."); + } finally { + writeFileSync(filePath, content); + } + + results = await eslint.lintFiles(['**/*.gts', '**/*.ts']); + + resultErrors = results.flatMap((result) => result.messages); + expect(resultErrors).toHaveLength(3); + + expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead."); + expect(resultErrors[0].line).toBe(6); + + expect(resultErrors[1].message).toBe("Use 'String#startsWith' method instead."); + expect(resultErrors[1].line).toBe(7); + + expect(resultErrors[2].line).toBe(8); + expect(resultErrors[2].message).toBe("Use 'String#startsWith' method instead."); }); }); diff --git a/tests/lib/rules-preprocessor/tsconfig.eslint.json b/tests/lib/rules-preprocessor/tsconfig.eslint.json index 23f2d756df..b58959351c 100644 --- a/tests/lib/rules-preprocessor/tsconfig.eslint.json +++ b/tests/lib/rules-preprocessor/tsconfig.eslint.json @@ -5,6 +5,7 @@ "strictNullChecks": true }, "include": [ - "**/*" + "**/*.ts", + "**/*.gts" ], }