@@ -89,10 +89,10 @@ export class BaseConfigFactory {
8989 configArray . push ( ...this . createJavascriptConfigArray ( ) ) ;
9090 } else if ( this . useLwcBaseConfig ( ) ) {
9191 configArray . push ( ...this . createLwcConfigArray ( ) ) ;
92- } else if ( this . engineConfig . file_extensions . javascript . length > 0 ) {
92+ } else {
9393 // When both base configs are disabled, we still need to configure a parser for JavaScript files
94- // to avoid ESLint falling back to Espree which can't parse decorators
95- configArray . push ( ...this . createMinimalJavascriptParserConfig ( ) ) ;
94+ // to avoid ESLint falling back to Espree which can't parse decorators (returns [] if no JS extensions)
95+ configArray . push ( ...this . createJavascriptConfigArray ( true ) ) ;
9696 }
9797 if ( this . useSldsCSSBaseConfig ( ) ) {
9898 configArray . push ( ...this . createSldsCSSConfigArray ( ) ) ;
@@ -105,7 +105,7 @@ export class BaseConfigFactory {
105105 } else if ( this . engineConfig . file_extensions . typescript . length > 0 ) {
106106 // When TS base config is disabled, we still need to configure a parser for TypeScript files
107107 // to avoid ESLint falling back to a parser that can't handle TypeScript syntax
108- configArray . push ( ...this . createMinimalTypescriptParserConfig ( ) ) ;
108+ configArray . push ( ...this . createTypescriptConfigArray ( true ) ) ;
109109 }
110110 // Add React plugin config for JSX files
111111 if ( this . useReactBaseConfig ( ) ) {
@@ -197,11 +197,20 @@ export class BaseConfigFactory {
197197 return configs ;
198198 }
199199
200- private createJavascriptConfigArray ( ) : Linter . Config [ ] {
200+ /**
201+ * Builds JavaScript ESLint config. When parserOnly is true (both JS and LWC base configs disabled), returns
202+ * only parser config with no rules so ESLint doesn't fall back to Espree which can't parse decorators.
203+ */
204+ private createJavascriptConfigArray ( parserOnly ?: boolean ) : Linter . Config [ ] {
205+ const jsExtensions = this . engineConfig . file_extensions . javascript ;
206+ if ( parserOnly && jsExtensions . length === 0 ) {
207+ return [ ] ;
208+ }
201209 // Smart parser selection based on file extensions:
202210 // - .js files may contain LWC decorators (@api, @track, @wire) → need Babel
203211 // - .jsx, .mjs, .cjs are typically React or modules without decorators → can use Espree (faster)
204- const hasJsExtension = this . engineConfig . file_extensions . javascript . includes ( '.js' ) ;
212+ const allJsFilePatterns = jsExtensions . map ( ext => `**/*${ ext } ` ) ;
213+ const hasJsExtension = jsExtensions . includes ( '.js' ) ;
205214
206215 if ( hasJsExtension ) {
207216 // .js files might have LWC decorators - use Babel parser with decorator support
@@ -221,19 +230,21 @@ export class BaseConfigFactory {
221230 }
222231 } ;
223232
233+ const base = parserOnly ? { } : { ...eslintJs . configs . all } ;
224234 return [ {
225- ... eslintJs . configs . all ,
226- files : this . engineConfig . file_extensions . javascript . map ( ext => `**/* ${ ext } ` ) ,
235+ ...base ,
236+ files : allJsFilePatterns ,
227237 languageOptions : {
228238 parser : babelParser ,
229239 parserOptions : enhancedParserOptions
230240 }
231241 } ] ;
232242 } else {
233243 // Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance
244+ const base = parserOnly ? { } : { ...eslintJs . configs . all } ;
234245 return [ {
235- ... eslintJs . configs . all ,
236- files : this . engineConfig . file_extensions . javascript . map ( ext => `**/* ${ ext } ` ) ,
246+ ...base ,
247+ files : allJsFilePatterns ,
237248 languageOptions : {
238249 parserOptions : {
239250 ecmaFeatures : {
@@ -245,85 +256,41 @@ export class BaseConfigFactory {
245256 }
246257 }
247258
248- private createMinimalJavascriptParserConfig ( ) : Linter . Config [ ] {
249- // When both disable_javascript_base_config and disable_lwc_base_config are true,
250- // we still need to configure a parser for JavaScript files to avoid ESLint falling
251- // back to Espree which can't parse decorators. This method configures ONLY the parser,
252- // without applying any base JavaScript or LWC rules.
253- const hasJsExtension = this . engineConfig . file_extensions . javascript . includes ( '.js' ) ;
254-
255- if ( hasJsExtension ) {
256- // .js files might have LWC decorators - use Babel parser with decorator support
257- const lwcConfig = validateAndGetRawLwcConfigArray ( ) [ 0 ] ;
258- const babelParser = lwcConfig . languageOptions ?. parser ;
259- const originalParserOptions = lwcConfig . languageOptions ?. parserOptions as Linter . ParserOptions ;
260- const originalBabelOptions = originalParserOptions . babelOptions || { } ;
261-
262- // Add @babel /preset-react to support JSX in React files alongside LWC files
263- const enhancedParserOptions = {
264- ...originalParserOptions ,
265- babelOptions : {
266- ...originalBabelOptions ,
267- configFile : false ,
268- // Add React preset for JSX support (.jsx files and React in .js files)
269- presets : [ ...( originalBabelOptions . presets || [ ] ) , require . resolve ( '@babel/preset-react' ) ]
270- }
271- } ;
259+ /**
260+ * Builds TypeScript ESLint config. When parserOnly is true (TS base config disabled), returns only parser config
261+ * with no rules and no projectService so files outside tsconfig.json can be parsed; type-aware rules won't run.
262+ * Both paths use the same logic to build languageOptions/parserOptions; only rules and projectService differ.
263+ */
264+ private createTypescriptConfigArray ( parserOnly ?: boolean ) : Linter . Config [ ] {
265+ const filePatterns = this . engineConfig . file_extensions . typescript . map ( ext => `**/*${ ext } ` ) ;
266+ const configsToApply = ( parserOnly
267+ ? ( eslintTs . configs . all as Linter . Config [ ] )
268+ : ( [ eslintJs . configs . all , ...eslintTs . configs . all ] as Linter . Config [ ] ) ) ;
269+ const configs : Linter . Config [ ] = [ ] ;
272270
273- return [ {
274- files : this . engineConfig . file_extensions . javascript . map ( ext => `**/*${ ext } ` ) ,
275- languageOptions : {
276- parser : babelParser ,
277- parserOptions : enhancedParserOptions
278- }
279- } ] ;
280- } else {
281- // Only .jsx, .mjs, .cjs (no .js) - use Espree for better performance
282- return [ {
283- files : this . engineConfig . file_extensions . javascript . map ( ext => `**/*${ ext } ` ) ,
271+ for ( const conf of configsToApply ) {
272+ const baseLanguageOptions = conf . languageOptions ?? { } ;
273+ const baseParserOptions = ( baseLanguageOptions . parserOptions as Linter . ParserOptions ) ?? { } ;
274+ const parserOptions : Linter . ParserOptions = parserOnly
275+ ? ( ( ) => { const { projectService : _omit , ...rest } = baseParserOptions ; return rest ; } ) ( )
276+ : {
277+ ...baseParserOptions ,
278+ // Finds the tsconfig.json file nearest to each source file. This should work for most users.
279+ // If not, then we may consider letting user specify this via config or alternatively users can
280+ // just set disable_typescript_base_config=true and configure typescript in their own eslint
281+ // config file. See https://typescript-eslint.io/packages/parser/#projectservice
282+ projectService : true
283+ } ;
284+ configs . push ( {
285+ ...( parserOnly ? { } : conf ) ,
286+ files : filePatterns ,
284287 languageOptions : {
285- parserOptions : {
286- ecmaFeatures : {
287- jsx : true // Enable JSX parsing for React/JSX files
288- }
289- }
288+ ...baseLanguageOptions ,
289+ parserOptions
290290 }
291- } ] ;
291+ } ) ;
292292 }
293- }
294-
295- private createMinimalTypescriptParserConfig ( ) : Linter . Config [ ] {
296- // When disable_typescript_base_config is true, we still need to configure a parser
297- // for TypeScript files to avoid ESLint falling back to Espree which can't parse
298- // TypeScript syntax. This method configures ONLY the parser, without applying base rules.
299- //
300- // IMPORTANT LIMITATION - Type-Aware Rules:
301- // =========================================
302- // We intentionally do NOT set projectService here. The projectService option
303- // is used for type-aware linting, but it requires files to be in a tsconfig.json project.
304- // Without projectService, the TypeScript parser can still parse TypeScript syntax
305- // (decorators, type annotations, etc.) but ONLY non-type-aware rules can run.
306- //
307- // Type-aware rules (e.g., @typescript-eslint/await-thenable) will NOT work with this
308- // minimal config. If users need type-aware rules, they should enable the TypeScript
309- // base config (disable_typescript_base_config: false).
310- //
311- // This trade-off allows users to:
312- // ✓ Parse TypeScript files without a tsconfig.json
313- // ✓ Run basic ESLint rules on TypeScript code
314- // ✗ Cannot use type-aware TypeScript rules
315-
316- // Get the first TypeScript config which contains the parser setup
317- const tsConfig = ( eslintTs . configs . all as Linter . Config [ ] ) [ 0 ] ;
318-
319- return [ {
320- files : this . engineConfig . file_extensions . typescript . map ( ext => `**/*${ ext } ` ) ,
321- languageOptions : {
322- ...( tsConfig . languageOptions ?? { } )
323- // Explicitly omit parserOptions.projectService to allow parsing files
324- // that aren't part of a TypeScript project
325- }
326- } ] ;
293+ return configs ;
327294 }
328295
329296 private createSldsHTMLConfigArray ( ) : Linter . Config [ ] {
@@ -344,29 +311,6 @@ export class BaseConfigFactory {
344311 } ) ;
345312 }
346313
347- private createTypescriptConfigArray ( ) : Linter . Config [ ] {
348- const configs : Linter . Config [ ] = [ ] ;
349- for ( const conf of ( [ eslintJs . configs . all , ...eslintTs . configs . all ] as Linter . Config [ ] ) ) {
350- configs . push ( {
351- ...conf ,
352- files : this . engineConfig . file_extensions . typescript . map ( ext => `**/*${ ext } ` ) ,
353- languageOptions : {
354- ... ( conf . languageOptions ?? { } ) ,
355- parserOptions : {
356- ... ( conf . languageOptions ?. parserOptions ?? { } ) ,
357-
358- // Finds the tsconfig.json file nearest to each source file. This should work for most users.
359- // If not, then we may consider letting user specify this via config or alternatively users can
360- // just set disable_typescript_base_config=true and configure typescript in their own eslint
361- // config file. See https://typescript-eslint.io/packages/parser/#projectservice
362- projectService : true
363- }
364- }
365- } ) ;
366- }
367- return configs ;
368- }
369-
370314 /**
371315 * Creates React plugin config for JavaScript and TypeScript files.
372316 *
0 commit comments