Repository: rollup/rollup
File: src/ast/nodes/ImportExpression.ts (TypeScript)

Code before:
import type * as NodeType from './NodeType';
import ObjectPattern from './ObjectPattern';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
	doNotDeoptimize,
	type ExpressionNode,
	type GenericEsTreeNode,
	type IncludeChildren,
	NodeBase
} from './shared/Node';
import VariableDeclarator from './VariableDeclarator';

interface DynamicImportMechanism {
	left: string;
	right: string;
}

export default class ImportExpression extends NodeBase {
	declare options: ExpressionNode | null;
	inlineNamespace: NamespaceVariable | null = null;
	get isFollowingTopLevelAwait(): boolean {
		return isFlagSet(this.flags, Flag.isFollowingTopLevelAwait);
	}
	set isFollowingTopLevelAwait(value: boolean) {
		this.flags = setFlag(this.flags, Flag.isFollowingTopLevelAwait, value);
	}
	declare source: ExpressionNode;
	declare type: NodeType.tImportExpression;
	declare sourceAstNode: AstNode;

	private hasUnknownAccessedKey = false;
	private accessedPropKey = new Set<string>();
	private attributes: string | null | true = null;
	private mechanism: DynamicImportMechanism | null = null;
	private namespaceExportName: string | false | undefined = undefined;
	private resolution: Module | ExternalModule | string | null = null;
	private resolutionString: string | null = null;

	// Do not bind attributes
	bind(): void {
		this.source.bind();
	}

	/**
	 * Get imported variables for deterministic usage, valid cases are:
	 *
	 * - `const { foo } = await import('bar')`.
	 * - `(await import('bar')).foo`
	 * - `import('bar').then(({ foo }) => {})`
	 *
	 * Returns empty array if it's side-effect only import.

Review comment (suggestion):
I wonder if we really need a bit-flag here as it is rather trivial to calculate on the fly. What if instead we have a method

```ts
getIsFollowingTopLevelAwait(): boolean {
  return this.parent instanceof AwaitExpression && this.parent.isTopLevelAwait
}
```

and call that from `Module.includeDynamicImport`? I think in the current code, it will only ever be called once for every dynamic import on first inclusion. But this also removes an indirect dependency where we need to make sure the `ImportExpression` is included before the dynamic import is included.

Code after:
import type * as NodeType from './NodeType';
import ObjectPattern from './ObjectPattern';
import {
	doNotDeoptimize,
	type ExpressionNode,
	type GenericEsTreeNode,
	type IncludeChildren,
	NodeBase
} from './shared/Node';
import VariableDeclarator from './VariableDeclarator';

interface DynamicImportMechanism {
	left: string;
	right: string;
}

export default class ImportExpression extends NodeBase {
	declare options: ExpressionNode | null;
	inlineNamespace: NamespaceVariable | null = null;
	declare source: ExpressionNode;
	declare type: NodeType.tImportExpression;
	declare sourceAstNode: AstNode;

	private hasUnknownAccessedKey = false;
	private accessedPropKey = new Set<string>();
	private attributes: string | null | true = null;
	private mechanism: DynamicImportMechanism | null = null;
	private namespaceExportName: string | false | undefined = undefined;
	private resolution: Module | ExternalModule | string | null = null;
	private resolutionString: string | null = null;

	// Do not bind attributes
	bind(): void {
		this.source.bind();
	}

	get isFollowingTopLevelAwait() {
		return this.parent instanceof AwaitExpression && this.parent.isTopLevelAwait;
	}

	/**
	 * Get imported variables for deterministic usage, valid cases are:
	 *
	 * - `const { foo } = await import('bar')`.
	 * - `(await import('bar')).foo`
	 * - `import('bar').then(({ foo }) => {})`
	 *
	 * Returns empty array if it's side-effect only import.
	 * Returns undefined if it's not fully deterministic.
	 */
	getDeterministicImportedNames(): readonly string[] | undefined {