Skip to content

Commit bb32d7f

Browse files
committed
BUGFIX: Add TypeScript parser config when disable_typescript_base_config is true
Extended the previous fix to also handle TypeScript files. When users set disable_typescript_base_config: true, TypeScript files failed to parse with error: 'Unexpected token :' (from type annotations). Root Cause: Same as JavaScript bug - when the TypeScript base config is disabled, no TypeScript parser was configured, causing ESLint to use a parser that cannot handle TypeScript syntax. Solution: Added createMinimalTypescriptParserConfig() method that configures ONLY the TypeScript parser (without base rules or projectService) when the base config is disabled. Note: The minimal TypeScript config intentionally omits projectService to allow parsing TypeScript files that aren't part of a tsconfig.json project. This means type-aware rules won't work, but syntax parsing (decorators, type annotations) will function correctly. Changes: - base-config.ts: Added TypeScript fallback case and createMinimalTypescriptParserConfig() - parser-selection.test.ts: Added 2 tests for TypeScript with base config disabled - Test data: Added workspaceWithTsDecorators and workspaceWithTsx test fixtures Test Coverage: - TypeScript files with decorators when disable_typescript_base_config: true - TSX files with JSX when disable_typescript_base_config: true All 284 tests passing with 99.83% code coverage.
1 parent 9a7e104 commit bb32d7f

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

packages/code-analyzer-eslint-engine/src/base-config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export class BaseConfigFactory {
6060
}
6161
if (this.useTsBaseConfig()) {
6262
configArray.push(...this.createTypescriptConfigArray());
63+
} else if (this.engineConfig.file_extensions.typescript.length > 0) {
64+
// When TS base config is disabled, we still need to configure a parser for TypeScript files
65+
// to avoid ESLint falling back to a parser that can't handle TypeScript syntax
66+
configArray.push(...this.createMinimalTypescriptParserConfig());
6367
}
6468
// Add React plugin config for JSX files
6569
if (this.useReactBaseConfig()) {
@@ -246,6 +250,29 @@ export class BaseConfigFactory {
246250
}
247251
}
248252

253+
private createMinimalTypescriptParserConfig(): Linter.Config[] {
254+
// When disable_typescript_base_config is true, we still need to configure a parser
255+
// for TypeScript files to avoid ESLint falling back to Espree which can't parse
256+
// TypeScript syntax. This method configures ONLY the parser, without applying base rules.
257+
//
258+
// Note: We intentionally do NOT set projectService here. The projectService option
259+
// is used for type-aware linting, but it requires files to be in a tsconfig.json project.
260+
// Without projectService, the TypeScript parser can still parse TypeScript syntax
261+
// (decorators, type annotations, etc.) for non-type-aware rules.
262+
263+
// Get the first TypeScript config which contains the parser setup
264+
const tsConfig = (eslintTs.configs.all as Linter.Config[])[0];
265+
266+
return [{
267+
files: this.engineConfig.file_extensions.typescript.map(ext => `**/*${ext}`),
268+
languageOptions: {
269+
...(tsConfig.languageOptions ?? {})
270+
// Explicitly omit parserOptions.projectService to allow parsing files
271+
// that aren't part of a TypeScript project
272+
}
273+
}];
274+
}
275+
249276
private createSldsHTMLConfigArray(): Linter.Config[] {
250277
return sldsEslintPlugin.configs['flat/recommended-html'].map((htmlConfig: Linter.Config) => {
251278
return {

packages/code-analyzer-eslint-engine/test/parser-selection.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,4 +698,67 @@ describe('Parser Selection for Decorator Support', () => {
698698
expect(engine).toBeDefined();
699699
});
700700
});
701+
702+
describe('TypeScript with disable_typescript_base_config: Bug Check', () => {
703+
const workspaceWithTsDecorators: string = path.join(testDataFolder, 'workspaceWithTsDecorators');
704+
705+
it('should parse .ts files with decorators when disable_typescript_base_config is true', async () => {
706+
// This test checks if we have the same bug with TypeScript as we had with JavaScript
707+
// When disable_typescript_base_config: true, is a TypeScript parser still configured?
708+
const configWithTsDisabled: ConfigObject = {
709+
disable_typescript_base_config: true,
710+
file_extensions: {
711+
javascript: ['.js'],
712+
typescript: ['.ts'],
713+
html: ['.html'],
714+
css: ['.css'],
715+
other: []
716+
},
717+
config_root: __dirname
718+
};
719+
720+
const engine: Engine = await createEngineFromPlugin(configWithTsDisabled);
721+
const workspace: Workspace = new Workspace('test', [workspaceWithTsDecorators],
722+
[path.join(workspaceWithTsDecorators, 'tsComponent.ts')]);
723+
724+
const runOptions: RunOptions = createRunOptions(workspace);
725+
const results: EngineRunResults = await engine.runRules(['no-debugger'], runOptions);
726+
727+
// Assert: Should parse TypeScript decorators successfully
728+
expect(results.violations).toBeDefined();
729+
const parsingErrors = results.violations.filter(v =>
730+
v.message.includes('Parsing error') || v.message.includes('Unexpected character')
731+
);
732+
expect(parsingErrors.length).toBe(0);
733+
});
734+
735+
it('should parse .tsx files with JSX when disable_typescript_base_config is true', async () => {
736+
const workspaceWithTsx: string = path.join(testDataFolder, 'workspaceWithTsx');
737+
const configWithTsDisabled: ConfigObject = {
738+
disable_typescript_base_config: true,
739+
file_extensions: {
740+
javascript: ['.js'],
741+
typescript: ['.tsx'],
742+
html: ['.html'],
743+
css: ['.css'],
744+
other: []
745+
},
746+
config_root: __dirname
747+
};
748+
749+
const engine: Engine = await createEngineFromPlugin(configWithTsDisabled);
750+
const workspace: Workspace = new Workspace('test', [workspaceWithTsx],
751+
[path.join(workspaceWithTsx, 'ReactTsComponent.tsx')]);
752+
753+
const runOptions: RunOptions = createRunOptions(workspace);
754+
const results: EngineRunResults = await engine.runRules(['no-debugger'], runOptions);
755+
756+
// Assert: Should parse TSX (TypeScript + JSX) successfully
757+
expect(results.violations).toBeDefined();
758+
const parsingErrors = results.violations.filter(v =>
759+
v.message.includes('Parsing error')
760+
);
761+
expect(parsingErrors.length).toBe(0);
762+
});
763+
});
701764
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// TypeScript component with decorators - for testing ESLint parsing
2+
// @ts-nocheck
3+
function Component(target: any) {
4+
return target;
5+
}
6+
7+
function Property(target: any, propertyKey: string) {
8+
// Property decorator
9+
}
10+
11+
@Component
12+
class MyComponent {
13+
@Property
14+
public name: string;
15+
16+
constructor() {
17+
this.name = "Test Component";
18+
}
19+
20+
public greet(): string {
21+
return `Hello from ${this.name}`;
22+
}
23+
}
24+
25+
export default MyComponent;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @ts-nocheck
2+
interface Props {
3+
name: string;
4+
age?: number;
5+
}
6+
7+
const ReactTsComponent = ({ name, age }: Props) => {
8+
return (
9+
<div>
10+
<h1>Hello, {name}!</h1>
11+
{age && <p>Age: {age}</p>}
12+
</div>
13+
);
14+
};
15+
16+
export default ReactTsComponent;

0 commit comments

Comments
 (0)