diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx
index d865ebb717d..c00bd325b6a 100644
--- a/docs/packages/TypeScript_ESTree.mdx
+++ b/docs/packages/TypeScript_ESTree.mdx
@@ -33,6 +33,12 @@ Parses the given string of code with the options provided and returns an ESTree-
```ts
interface ParseOptions {
+ /**
+ * Specify the `sourceType`.
+ * For more details, see https://github.com/typescript-eslint/typescript-eslint/pull/9121
+ */
+ sourceType?: SourceType;
+
/**
* Prevents the parser from throwing an error if it receives an invalid AST from TypeScript.
* This case only usually occurs when attempting to lint invalid code.
diff --git a/packages/typescript-estree/src/create-program/createSourceFile.ts b/packages/typescript-estree/src/create-program/createSourceFile.ts
index bb5bc9d7b8f..096264c5844 100644
--- a/packages/typescript-estree/src/create-program/createSourceFile.ts
+++ b/packages/typescript-estree/src/create-program/createSourceFile.ts
@@ -23,6 +23,7 @@ function createSourceFile(parseSettings: ParseSettings): ts.SourceFile {
{
languageVersion: ts.ScriptTarget.Latest,
jsDocParsingMode: parseSettings.jsDocParsingMode,
+ setExternalModuleIndicator: parseSettings.setExternalModuleIndicator,
},
/* setParentNodes */ true,
getScriptKind(parseSettings.filePath, parseSettings.jsx),
diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts
index 2bae667104f..35783e0acbe 100644
--- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts
+++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts
@@ -1,3 +1,5 @@
+import path from 'node:path';
+
import debug from 'debug';
import * as ts from 'typescript';
@@ -46,6 +48,14 @@ export function createParseSettings(
? tsestreeOptions.tsconfigRootDir
: process.cwd();
const passedLoggerFn = typeof tsestreeOptions.loggerFn === 'function';
+ const filePath = ensureAbsolutePath(
+ typeof tsestreeOptions.filePath === 'string' &&
+ tsestreeOptions.filePath !== ''
+ ? tsestreeOptions.filePath
+ : getFileName(tsestreeOptions.jsx),
+ tsconfigRootDir,
+ );
+ const extension = path.extname(filePath).toLowerCase() as ts.Extension;
const jsDocParsingMode = ((): ts.JSDocParsingMode => {
switch (tsestreeOptions.jsDocParsingMode) {
case 'all':
@@ -81,13 +91,17 @@ export function createParseSettings(
tsestreeOptions.extraFileExtensions.every(ext => typeof ext === 'string')
? tsestreeOptions.extraFileExtensions
: [],
- filePath: ensureAbsolutePath(
- typeof tsestreeOptions.filePath === 'string' &&
- tsestreeOptions.filePath !== ''
- ? tsestreeOptions.filePath
- : getFileName(tsestreeOptions.jsx),
- tsconfigRootDir,
- ),
+ filePath,
+ setExternalModuleIndicator:
+ tsestreeOptions.sourceType === 'module' ||
+ (tsestreeOptions.sourceType === undefined &&
+ extension === ts.Extension.Mjs) ||
+ (tsestreeOptions.sourceType === undefined &&
+ extension === ts.Extension.Mts)
+ ? (file): void => {
+ file.externalModuleIndicator = true;
+ }
+ : undefined,
jsDocParsingMode,
jsx: tsestreeOptions.jsx === true,
loc: tsestreeOptions.loc === true,
diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts
index 41a21c04494..2e9b341d3dc 100644
--- a/packages/typescript-estree/src/parseSettings/index.ts
+++ b/packages/typescript-estree/src/parseSettings/index.ts
@@ -72,6 +72,14 @@ export interface MutableParseSettings {
*/
filePath: string;
+ /**
+ * Sets the external module indicator on the source file.
+ * Used by Typescript to determine if a sourceFile is an external module.
+ *
+ * needed to always parsing `mjs`/`mts` files as ESM
+ */
+ setExternalModuleIndicator?: (file: ts.SourceFile) => void;
+
/**
* JSDoc parsing style to pass through to TypeScript
*/
diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts
index 55f4d3b05eb..44af134611c 100644
--- a/packages/typescript-estree/src/parser-options.ts
+++ b/packages/typescript-estree/src/parser-options.ts
@@ -3,6 +3,7 @@ import type {
DebugLevel,
JSDocParsingMode,
ProjectServiceOptions,
+ SourceType,
} from '@typescript-eslint/types';
import type * as ts from 'typescript';
@@ -15,6 +16,12 @@ export type { ProjectServiceOptions } from '@typescript-eslint/types';
//////////////////////////////////////////////////////////
interface ParseOptions {
+ /**
+ * Specify the `sourceType`.
+ * For more details, see https://github.com/typescript-eslint/typescript-eslint/pull/9121
+ */
+ sourceType?: SourceType;
+
/**
* Prevents the parser from throwing an error if it receives an invalid AST from TypeScript.
* This case only usually occurs when attempting to lint invalid code.
diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts
index dc750b9f9b6..fe9883ffe5a 100644
--- a/packages/typescript-estree/tests/lib/parse.test.ts
+++ b/packages/typescript-estree/tests/lib/parse.test.ts
@@ -342,6 +342,134 @@ describe('parseAndGenerateServices', () => {
});
});
+ describe('ESM parsing', () => {
+ describe('TLA(Top Level Await)', () => {
+ const config: TSESTreeOptions = {
+ projectService: false,
+ comment: true,
+ tokens: true,
+ range: true,
+ loc: true,
+ };
+ const code = 'await(1)';
+
+ const testParse = ({
+ sourceType,
+ ext,
+ shouldAllowTLA = false,
+ }: {
+ sourceType?: 'module' | 'script';
+ ext: '.js' | '.ts' | '.mjs' | '.mts';
+ shouldAllowTLA?: boolean;
+ }): void => {
+ const ast = parser.parse(code, {
+ ...config,
+ sourceType,
+ filePath: `file${ext}`,
+ });
+ const expressionType = (
+ ast.body[0] as parser.TSESTree.ExpressionStatement
+ ).expression.type;
+
+ it(`parse(): should ${
+ shouldAllowTLA ? 'allow' : 'not allow'
+ } TLA for ${ext} file with sourceType = ${sourceType}`, () => {
+ expect(expressionType).toBe(
+ shouldAllowTLA
+ ? parser.AST_NODE_TYPES.AwaitExpression
+ : parser.AST_NODE_TYPES.CallExpression,
+ );
+ });
+ };
+ const testParseAndGenerateServices = ({
+ sourceType,
+ ext,
+ shouldAllowTLA = false,
+ }: {
+ sourceType?: 'module' | 'script';
+ ext: '.js' | '.ts' | '.mjs' | '.mts';
+ shouldAllowTLA?: boolean;
+ }): void => {
+ const result = parser.parseAndGenerateServices(code, {
+ ...config,
+ sourceType,
+ filePath: `file${ext}`,
+ });
+ const expressionType = (
+ result.ast.body[0] as parser.TSESTree.ExpressionStatement
+ ).expression.type;
+
+ it(`parseAndGenerateServices(): should ${
+ shouldAllowTLA ? 'allow' : 'not allow'
+ } TLA for ${ext} file with sourceType = ${sourceType}`, () => {
+ expect(expressionType).toBe(
+ shouldAllowTLA
+ ? parser.AST_NODE_TYPES.AwaitExpression
+ : parser.AST_NODE_TYPES.CallExpression,
+ );
+ });
+ };
+
+ testParse({ ext: '.js' });
+ testParse({ ext: '.ts' });
+ testParse({ ext: '.mjs', shouldAllowTLA: true });
+ testParse({ ext: '.mts', shouldAllowTLA: true });
+
+ testParse({ sourceType: 'module', ext: '.js', shouldAllowTLA: true });
+ testParse({ sourceType: 'module', ext: '.ts', shouldAllowTLA: true });
+ testParse({ sourceType: 'module', ext: '.mjs', shouldAllowTLA: true });
+ testParse({ sourceType: 'module', ext: '.mts', shouldAllowTLA: true });
+
+ testParse({ sourceType: 'script', ext: '.js' });
+ testParse({ sourceType: 'script', ext: '.ts' });
+ testParse({ sourceType: 'script', ext: '.mjs' });
+ testParse({ sourceType: 'script', ext: '.mts' });
+
+ testParseAndGenerateServices({ ext: '.js' });
+ testParseAndGenerateServices({ ext: '.ts' });
+ testParseAndGenerateServices({ ext: '.mjs', shouldAllowTLA: true });
+ testParseAndGenerateServices({ ext: '.mts', shouldAllowTLA: true });
+
+ testParseAndGenerateServices({
+ sourceType: 'module',
+ ext: '.js',
+ shouldAllowTLA: true,
+ });
+ testParseAndGenerateServices({
+ sourceType: 'module',
+ ext: '.ts',
+ shouldAllowTLA: true,
+ });
+ testParseAndGenerateServices({
+ sourceType: 'module',
+ ext: '.mjs',
+ shouldAllowTLA: true,
+ });
+ testParseAndGenerateServices({
+ sourceType: 'module',
+ ext: '.mts',
+ shouldAllowTLA: true,
+ });
+
+ testParseAndGenerateServices({
+ sourceType: 'script',
+ ext: '.js',
+ });
+ testParseAndGenerateServices({
+ sourceType: 'script',
+ ext: '.ts',
+ });
+ testParseAndGenerateServices({
+ sourceType: 'script',
+ ext: '.mjs',
+ });
+ testParseAndGenerateServices({
+ sourceType: 'script',
+ ext: '.mts',
+ });
+ });
+ });
+
if (process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE !== 'true') {
describe('invalid file error messages', () => {
const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors');
diff --git a/packages/typescript-estree/typings/typescript.d.ts b/packages/typescript-estree/typings/typescript.d.ts
index 8d370d1ea69..039c9a06259 100644
--- a/packages/typescript-estree/typings/typescript.d.ts
+++ b/packages/typescript-estree/typings/typescript.d.ts
@@ -3,7 +3,7 @@ import 'typescript';
// these additions are marked as internal to typescript
declare module 'typescript' {
interface SourceFile {
- externalModuleIndicator?: Node;
+ externalModuleIndicator?: Node | true;
parseDiagnostics: DiagnosticWithLocation[];
}