ember-eslint-parser 0.5.10 → 0.5.12

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": "ember-eslint-parser",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -20,6 +20,7 @@
20
20
  "dependencies": {
21
21
  "@babel/eslint-parser": "^7.23.10",
22
22
  "@glimmer/syntax": ">= 0.92.0",
23
+ "@typescript-eslint/tsconfig-utils": "^8.38.0",
23
24
  "content-tag": "^2.0.1",
24
25
  "eslint-scope": "^7.2.2",
25
26
  "html-tags": "^3.3.1",
@@ -43,7 +44,7 @@
43
44
  "fs-extra": "^11.2.0",
44
45
  "prettier": "^3.2.5",
45
46
  "publint": "^0.2.6",
46
- "release-plan": "^0.9.0",
47
+ "release-plan": "^0.16.0",
47
48
  "typescript": "^5.3.3",
48
49
  "vite": "^5.0.12",
49
50
  "vitest": "^1.2.2"
@@ -1,3 +1,4 @@
1
+ const tsconfigUtils = require('@typescript-eslint/tsconfig-utils');
1
2
  const babelParser = require('@babel/eslint-parser');
2
3
  const { registerParsedFile } = require('../preprocessor/noop');
3
4
  const {
@@ -15,8 +16,119 @@ const { transformForLint, preprocessGlimmerTemplates, convertAst } = require('./
15
16
  * 3. preprocesses the templates info and prepares the Glimmer AST
16
17
  * 4. converts the js/ts AST so that it includes the Glimmer AST at the right locations, replacing the original
17
18
  */
19
+
20
+ /**
21
+ * @param {string} tsconfigPath
22
+ * @param {string} rootDir
23
+ * @returns {boolean|undefined}
24
+ */
25
+ function parseAllowJsFromTsconfig(tsconfigPath, rootDir) {
26
+ try {
27
+ const parserPath = require.resolve('@typescript-eslint/parser');
28
+ // eslint-disable-next-line n/no-unpublished-require
29
+ const tsPath = require.resolve('typescript', { paths: [parserPath] });
30
+ const ts = require(tsPath);
31
+ const parsed = tsconfigUtils.getParsedConfigFile(ts, tsconfigPath, rootDir);
32
+ return parsed?.options?.allowJs;
33
+ } catch (e) {
34
+ // eslint-disable-next-line no-console
35
+ console.warn('[ember-eslint-parser] Failed to parse tsconfig:', tsconfigPath, e);
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * @param {Array<boolean|undefined>} values
42
+ * @param {string} source
43
+ * @returns {boolean|null}
44
+ */
45
+ function resolveAllowJs(values, source) {
46
+ const filtered = values.filter((val) => typeof val !== 'undefined');
47
+ if (filtered.length > 0) {
48
+ const uniqueValues = [...new Set(filtered)];
49
+ if (uniqueValues.length > 1) {
50
+ // eslint-disable-next-line no-console
51
+ console.warn(
52
+ `[ember-eslint-parser] Conflicting allowJs values in ${source}. Defaulting allowGjs to false.`
53
+ );
54
+ return false;
55
+ } else {
56
+ return uniqueValues[0];
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * @param {Array<{getCompilerOptions?: Function}>|undefined} programs
64
+ * @returns {boolean|null}
65
+ */
66
+ function getAllowJsFromPrograms(programs) {
67
+ if (!Array.isArray(programs) || programs.length === 0) return null;
68
+ const allowJsValues = programs
69
+ .map((p) => p.getCompilerOptions?.())
70
+ .filter(Boolean)
71
+ .map((opts) => opts.allowJs);
72
+ return resolveAllowJs(allowJsValues, 'programs');
73
+ }
74
+
75
+ /**
76
+ * @param {boolean|object|undefined} projectService
77
+ * @returns {string|null}
78
+ */
79
+ function getProjectServiceTsconfigPath(projectService) {
80
+ if (!projectService) return null;
81
+
82
+ // If projectService is true, use default behavior (nearest tsconfig.json, allowJs from config)
83
+ if (projectService === true) {
84
+ return 'tsconfig.json';
85
+ }
86
+
87
+ // If projectService is an object, handle ProjectServiceOptions
88
+ if (typeof projectService === 'object') {
89
+ if (typeof projectService.allowDefaultProject !== 'undefined') {
90
+ // eslint-disable-next-line no-console
91
+ console.warn(
92
+ '[ember-eslint-parser] projectService.allowDefaultProject is specified. Behavior may differ depending on default project config.'
93
+ );
94
+ }
95
+ return projectService.defaultProject ?? 'tsconfig.json';
96
+ }
97
+
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * Returns the resolved allowJs value based on priority: programs > projectService > project/tsconfig
103
+ */
104
+ function getAllowJs(options) {
105
+ const allowJsFromPrograms = getAllowJsFromPrograms(options.programs);
106
+ if (allowJsFromPrograms !== null) return allowJsFromPrograms;
107
+
108
+ const rootDir = options.tsconfigRootDir || process.cwd();
109
+
110
+ const projectServiceTsconfigPath = getProjectServiceTsconfigPath(options.projectService);
111
+ if (projectServiceTsconfigPath) {
112
+ return parseAllowJsFromTsconfig(projectServiceTsconfigPath, rootDir);
113
+ }
114
+
115
+ let tsconfigPaths = [];
116
+ if (Array.isArray(options.project)) {
117
+ tsconfigPaths = options.project;
118
+ } else if (typeof options.project === 'string') {
119
+ tsconfigPaths = [options.project];
120
+ } else if (options.project) {
121
+ tsconfigPaths = ['tsconfig.json'];
122
+ }
123
+ if (tsconfigPaths.length > 0) {
124
+ const allowJsValues = tsconfigPaths.map((cfg) => parseAllowJsFromTsconfig(cfg, rootDir));
125
+ return resolveAllowJs(allowJsValues, 'project');
126
+ }
127
+
128
+ return false;
129
+ }
130
+
18
131
  /**
19
- *
20
132
  * @type {import('eslint').ParserModule}
21
133
  */
22
134
  module.exports = {
@@ -26,7 +138,13 @@ module.exports = {
26
138
  },
27
139
 
28
140
  parseForESLint(code, options) {
29
- patchTs();
141
+ const allowGjsWasSet = options.allowGjs !== undefined;
142
+ const allowGjs = allowGjsWasSet ? options.allowGjs : getAllowJs(options);
143
+ let actualAllowGjs;
144
+ // Only patch TypeScript if we actually need it.
145
+ if (options.programs || options.projectService || options.project) {
146
+ ({ allowGjs: actualAllowGjs } = patchTs({ allowGjs }));
147
+ }
30
148
  registerParsedFile(options.filePath);
31
149
  let jsCode = code;
32
150
  const info = transformForLint(code, options.filePath);
@@ -41,7 +159,7 @@ module.exports = {
41
159
 
42
160
  let result = null;
43
161
  const filePath = options.filePath;
44
- if (options.project) {
162
+ if (options.project || options.projectService) {
45
163
  jsCode = replaceExtensions(jsCode);
46
164
  }
47
165
 
@@ -73,7 +191,21 @@ module.exports = {
73
191
  result.isTypescript = isTypescript || useTypescript;
74
192
  convertAst(result, preprocessedResult, visitorKeys);
75
193
  if (result.services?.program) {
76
- syncMtsGtsSourceFiles(result.services?.program);
194
+ // Compare allowJs with the actual program's compiler options
195
+ const programAllowJs = result.services.program.getCompilerOptions?.()?.allowJs;
196
+ if (
197
+ !allowGjsWasSet &&
198
+ programAllowJs !== undefined &&
199
+ actualAllowGjs !== undefined &&
200
+ actualAllowGjs !== programAllowJs
201
+ ) {
202
+ // eslint-disable-next-line no-console
203
+ console.warn(
204
+ '[ember-eslint-parser] allowJs does not match the actual program. Consider setting allowGjs explicitly.\n' +
205
+ ` Current: ${allowGjs}, Program: ${programAllowJs}`
206
+ );
207
+ }
208
+ syncMtsGtsSourceFiles(result.services.program);
77
209
  }
78
210
  return { ...result, visitorKeys };
79
211
  } catch (e) {
@@ -2,7 +2,7 @@ const fs = require('node:fs');
2
2
  const { transformForLint } = require('./transforms');
3
3
  const { replaceRange } = require('./transforms');
4
4
 
5
- let patchTs, replaceExtensions, syncMtsGtsSourceFiles, typescriptParser, isPatched;
5
+ let patchTs, replaceExtensions, syncMtsGtsSourceFiles, typescriptParser, isPatched, allowGjs;
6
6
 
7
7
  try {
8
8
  const parserPath = require.resolve('@typescript-eslint/parser');
@@ -10,23 +10,27 @@ try {
10
10
  const tsPath = require.resolve('typescript', { paths: [parserPath] });
11
11
  const ts = require(tsPath);
12
12
  typescriptParser = require('@typescript-eslint/parser');
13
- patchTs = function patchTs() {
14
- if (isPatched) {
15
- return;
16
- }
13
+ patchTs = function patchTs(options = {}) {
14
+ if (isPatched) return { allowGjs };
17
15
  isPatched = true;
16
+ allowGjs = options.allowGjs !== undefined ? options.allowGjs : true;
18
17
  const sys = { ...ts.sys };
19
18
  const newSys = {
20
19
  ...ts.sys,
21
20
  readDirectory(...args) {
22
21
  const results = sys.readDirectory.call(this, ...args);
23
- return [
24
- ...results,
25
- ...results.filter((x) => x.endsWith('.gts')).map((f) => f.replace(/\.gts$/, '.mts')),
26
- ];
22
+ const gtsVirtuals = results
23
+ .filter((x) => x.endsWith('.gts'))
24
+ .map((f) => f.replace(/\.gts$/, '.mts'));
25
+ const gjsVirtuals = allowGjs
26
+ ? results.filter((x) => x.endsWith('.gjs')).map((f) => f.replace(/\.gjs$/, '.mjs'))
27
+ : [];
28
+ return results.concat(gtsVirtuals, gjsVirtuals);
27
29
  },
28
30
  fileExists(fileName) {
29
- return fs.existsSync(fileName.replace(/\.m?ts$/, '.gts')) || fs.existsSync(fileName);
31
+ const gtsExists = fs.existsSync(fileName.replace(/\.m?ts$/, '.gts'));
32
+ const gjsExists = allowGjs ? fs.existsSync(fileName.replace(/\.m?js$/, '.gjs')) : false;
33
+ return gtsExists || gjsExists || fs.existsSync(fileName);
30
34
  },
31
35
  readFile(fname) {
32
36
  let fileName = fname;
@@ -38,25 +42,30 @@ try {
38
42
  try {
39
43
  content = fs.readFileSync(fileName).toString();
40
44
  } catch {
41
- fileName = fileName.replace(/\.m?ts$/, '.gts');
45
+ if (fileName.match(/\.m?ts$/)) {
46
+ fileName = fileName.replace(/\.m?ts$/, '.gts');
47
+ } else if (allowGjs && fileName.match(/\.m?js$/)) {
48
+ fileName = fileName.replace(/\.m?js$/, '.gjs');
49
+ }
42
50
  content = fs.readFileSync(fileName).toString();
43
51
  }
44
- if (fileName.endsWith('.gts')) {
52
+ if (fileName.endsWith('.gts') || (allowGjs && fileName.endsWith('.gjs'))) {
45
53
  try {
46
54
  content = transformForLint(content).output;
47
55
  } catch (e) {
48
- console.error('failed to transformForLint for gts processing');
56
+ console.error('failed to transformForLint for gts/gjs processing');
49
57
  console.error(e);
50
58
  }
51
59
  }
52
60
  if (
53
61
  (!fileName.endsWith('.d.ts') && fileName.endsWith('.ts')) ||
54
- fileName.endsWith('.gts')
62
+ fileName.endsWith('.gts') ||
63
+ (allowGjs && fileName.endsWith('.gjs'))
55
64
  ) {
56
65
  try {
57
66
  content = replaceExtensions(content);
58
67
  } catch (e) {
59
- console.error('failed to replace extensions for gts processing');
68
+ console.error('failed to replace extensions for gts/gjs processing');
60
69
  console.error(e);
61
70
  }
62
71
  }
@@ -64,6 +73,7 @@ try {
64
73
  },
65
74
  };
66
75
  ts.setSys(newSys);
76
+ return { allowGjs };
67
77
  };
68
78
 
69
79
  replaceExtensions = function replaceExtensions(code) {
@@ -88,35 +98,42 @@ try {
88
98
  */
89
99
  syncMtsGtsSourceFiles = function syncMtsGtsSourceFiles(program) {
90
100
  const sourceFiles = program.getSourceFiles();
91
- for (const sourceFile of sourceFiles) {
92
- // check for deleted gts files, need to remove mts as well
93
- if (sourceFile.path.match(/\.m?ts$/) && sourceFile.isVirtualGts) {
94
- const gtsFile = program.getSourceFile(sourceFile.path.replace(/\.m?ts$/, '.gts'));
95
- if (!gtsFile) {
101
+ function syncVirtualFile(sourceFile, ext, virtualExt, virtualFlag) {
102
+ // check for deleted files, need to remove virtual as well
103
+ if (sourceFile.path.match(new RegExp(`\\.m?${virtualExt}$`)) && sourceFile[virtualFlag]) {
104
+ const origFile = program.getSourceFile(
105
+ sourceFile.path.replace(new RegExp(`\\.m?${virtualExt}$`), `.${ext}`)
106
+ );
107
+ if (!origFile) {
96
108
  sourceFile.version = null;
97
109
  }
98
110
  }
99
- if (sourceFile.path.endsWith('.gts')) {
100
- /**
101
- * @type {ts.SourceFile}
102
- */
103
- let mtsSourceFile = program.getSourceFile(sourceFile.path.replace(/\.gts$/, '.mts'));
104
- if (!mtsSourceFile) {
105
- mtsSourceFile = program.getSourceFile(sourceFile.path.replace(/\.gts$/, '.ts'));
111
+ if (sourceFile.path.endsWith(`.${ext}`)) {
112
+ let virtualSourceFile = program.getSourceFile(
113
+ sourceFile.path.replace(new RegExp(`\\.${ext}$`), `.${virtualExt}`)
114
+ );
115
+ if (!virtualSourceFile) {
116
+ virtualSourceFile = program.getSourceFile(
117
+ sourceFile.path.replace(new RegExp(`\\.${ext}$`), virtualExt === 'mts' ? '.ts' : '.js')
118
+ );
106
119
  }
107
- if (mtsSourceFile) {
120
+ if (virtualSourceFile) {
108
121
  const keep = {
109
- fileName: mtsSourceFile.fileName,
110
- path: mtsSourceFile.path,
111
- originalFileName: mtsSourceFile.originalFileName,
112
- resolvedPath: mtsSourceFile.resolvedPath,
113
- impliedNodeFormat: mtsSourceFile.impliedNodeFormat,
122
+ fileName: virtualSourceFile.fileName,
123
+ path: virtualSourceFile.path,
124
+ originalFileName: virtualSourceFile.originalFileName,
125
+ resolvedPath: virtualSourceFile.resolvedPath,
126
+ impliedNodeFormat: virtualSourceFile.impliedNodeFormat,
114
127
  };
115
- Object.assign(mtsSourceFile, sourceFile, keep);
116
- mtsSourceFile.isVirtualGts = true;
128
+ Object.assign(virtualSourceFile, sourceFile, keep);
129
+ virtualSourceFile[virtualFlag] = true;
117
130
  }
118
131
  }
119
132
  }
133
+ for (const sourceFile of sourceFiles) {
134
+ syncVirtualFile(sourceFile, 'gts', 'mts', 'isVirtualGts');
135
+ syncVirtualFile(sourceFile, 'gjs', 'mjs', 'isVirtualGjs');
136
+ }
120
137
  };
121
138
  } catch /* istanbul ignore next */ {
122
139
  // typescript not available