Skip to content

Commit

Permalink
feat: parse type import and export
Browse files Browse the repository at this point in the history
  • Loading branch information
Solo-steven committed Nov 3, 2024
1 parent 1705b1b commit 3902b11
Show file tree
Hide file tree
Showing 51 changed files with 211 additions and 62 deletions.
3 changes: 3 additions & 0 deletions web-infras/common/src/ast/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Declaration, ClassDeclaration, FunctionDeclaration, TSDeclaration } fro
export interface ImportDeclaration extends ModuleItem {
kind: SyntaxKinds.ImportDeclaration;
specifiers: Array<ImportDefaultSpecifier | ImportNamespaceSpecifier | ImportSpecifier>;
importKind: "type" | "value";
source: StringLiteral;
attributes: ImportAttribute[] | undefined;
}
Expand All @@ -25,6 +26,7 @@ export interface ImportDefaultSpecifier extends ModuleItem {
export interface ImportSpecifier extends ModuleItem {
kind: SyntaxKinds.ImportSpecifier;
imported: Identifier | StringLiteral;
isTypeOnly: boolean;
local: Identifier | null;
}
export interface ImportNamespaceSpecifier extends ModuleItem {
Expand All @@ -44,6 +46,7 @@ export interface ExportNamedDeclarations extends ModuleItem {
export interface ExportSpecifier extends ModuleItem {
kind: SyntaxKinds.ExportSpecifier;
exported: Identifier | StringLiteral;
isTypeOnly: boolean;
local: Identifier | StringLiteral | null;
}
export interface ExportDefaultDeclaration extends ModuleItem {
Expand Down
7 changes: 7 additions & 0 deletions web-infras/common/src/factory/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ export function createProgram(
export function createImportDeclaration(
specifiers: AST.ImportDeclaration["specifiers"],
source: AST.ImportDeclaration["source"],
importKind: AST.ImportDeclaration["importKind"],
attributes: AST.ImportDeclaration["attributes"],
start: SourcePosition,
end: SourcePosition,
): AST.ImportDeclaration {
return {
kind: SyntaxKinds.ImportDeclaration,
specifiers,
importKind,
source,
attributes,
start,
Expand Down Expand Up @@ -52,13 +54,15 @@ export function createImportNamespaceSpecifier(
export function createImportSpecifier(
imported: AST.ImportSpecifier["imported"],
local: AST.ImportSpecifier["local"],
isTypeOnly: AST.ImportSpecifier["isTypeOnly"],
start: SourcePosition,
end: SourcePosition,
): AST.ImportSpecifier {
return {
kind: SyntaxKinds.ImportSpecifier,
imported,
local,
isTypeOnly,
start,
end,
};
Expand All @@ -82,6 +86,7 @@ export function createExportNamedDeclaration(
specifiers: AST.ExportNamedDeclarations["specifiers"],
declaration: AST.ExportNamedDeclarations["declaration"],
source: AST.ExportNamedDeclarations["source"],

start: SourcePosition,
end: SourcePosition,
): AST.ExportNamedDeclarations {
Expand All @@ -97,12 +102,14 @@ export function createExportNamedDeclaration(
export function createExportSpecifier(
exported: AST.ExportSpecifier["exported"],
local: AST.ExportSpecifier["local"],
isTypeOnly: AST.ExportSpecifier["isTypeOnly"],
start: SourcePosition,
end: SourcePosition,
): AST.ExportSpecifier {
return {
kind: SyntaxKinds.ExportSpecifier,
exported,
isTypeOnly,
local,
start,
end,
Expand Down
5 changes: 3 additions & 2 deletions web-infras/parser/src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { transformSyntaxKindToLiteral } from "../tests/parserRunner/helpers/tran
import fs from "fs";
import path from "path";
import { ParserPlugin } from "./parser/config";
import { createErrorHandler } from "./errorHandler";
// import { ParserPlugin } from "./parser/config";
const code = fs.readFileSync(path.join(__dirname, "test.ts")).toString();
// console.log(code);
Expand Down Expand Up @@ -32,7 +33,7 @@ function printLexer(code: string) {
console.log("============ lexer ==============");
console.log("=================================");

const lexer = createLexer(code);
const lexer = createLexer(code, createErrorHandler(code));
while (lexer.getTokenKind() != SyntaxKinds.EOFToken) {
console.log(
lexer.getTokenKind(),
Expand All @@ -51,7 +52,7 @@ function printLexer(code: string) {
);
}
function printParser(code: string) {
const ast = parse(code, { sourceType: "script", plugins: [ParserPlugin.TypeScript] });
const ast = parse(code, { sourceType: "module", plugins: [ParserPlugin.TypeScript] });
transformSyntaxKindToLiteral(ast);
const astJsonString = JSON.stringify(ast, null, 4);
fs.writeFileSync("./test.json", astJsonString);
Expand Down
2 changes: 2 additions & 0 deletions web-infras/parser/src/parser/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,6 @@ export const ErrorMessageMap = {
"TS 1245: Method {} cannot have an implementation because it is marked abstract.",
ts_1267_property_cannot_have_an_initializer_because_it_is_marked_abstract:
"TS 1267: Property '{}' cannot have an initializer because it is marked abstract.",
ts_1363_A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both:
"TS 1363: A type-only import can specify a default import or named bindings, but not both.",
};
193 changes: 133 additions & 60 deletions web-infras/parser/src/parser/js/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import { ParserPlugin } from "@/src/parser/config";
import { ErrorMessageMap } from "@/src/parser/error";
import { ExportContext } from "@/src/parser/scope/lexicalScope";
import { KeywordSet } from "@/src/parser/type";
import { IdentiferWithKeyworArray, KeywordSet } from "@/src/parser/type";
import { Parser } from "@/src/parser";

export function parseProgram(this: Parser) {
Expand Down Expand Up @@ -114,6 +114,14 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
start,
);
}
let importKind: ImportDeclaration["importKind"] = "value";
if (this.isContextKeyword("type")) {
const { kind, value } = this.lookahead();
if (!((kind === SyntaxKinds.Identifier && value === "from") || kind === SyntaxKinds.StringLiteral)) {
this.nextToken();
importKind = "type";
}
}
const specifiers: Array<ImportDefaultSpecifier | ImportNamespaceSpecifier | ImportSpecifier> = [];
if (this.match(SyntaxKinds.StringLiteral)) {
const source = this.parseStringLiteral();
Expand All @@ -122,6 +130,7 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
return Factory.createImportDeclaration(
specifiers,
source,
importKind,
attributes,
start,
cloneSourcePosition(source.end),
Expand All @@ -136,6 +145,7 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
return Factory.createImportDeclaration(
specifiers,
source,
importKind,
attributes,
start,
cloneSourcePosition(source.end),
Expand All @@ -150,6 +160,7 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
return Factory.createImportDeclaration(
specifiers,
source,
importKind,
attributes,
start,
cloneSourcePosition(source.end),
Expand All @@ -159,8 +170,18 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
if (this.match(SyntaxKinds.CommaToken)) {
this.nextToken();
if (this.match(SyntaxKinds.BracesLeftPunctuator)) {
if (importKind === "type")
this.raiseError(
ErrorMessageMap.ts_1363_A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both,
this.getStartPosition(),
);
this.parseImportSpecifiers(specifiers);
} else if (this.match(SyntaxKinds.MultiplyOperator)) {
if (importKind === "type")
this.raiseError(
ErrorMessageMap.ts_1363_A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both,
this.getStartPosition(),
);
specifiers.push(this.parseImportNamespaceSpecifier());
} else {
throw this.createMessageError(
Expand All @@ -175,6 +196,7 @@ export function parseImportDeclaration(this: Parser): ImportDeclaration {
return Factory.createImportDeclaration(
specifiers,
source,
importKind,
attributes,
start,
cloneSourcePosition(source.end),
Expand Down Expand Up @@ -239,43 +261,90 @@ export function parseImportSpecifiers(
if (this.match(SyntaxKinds.BracesRightPunctuator) || this.match(SyntaxKinds.EOFToken)) {
break;
}
const imported = this.parseModuleExportName();
if (!this.isContextKeyword("as")) {
if (isIdentifer(imported) && KeywordSet.has(imported.name)) {
// recoverable error
this.raiseError(ErrorMessageMap.extra_error_unexpect_keyword_in_module_name, imported.start);
} else if (isStringLiteral(imported)) {
// recoverable error
this.raiseError(
ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_imported_binding,
imported.start,
);
}
if (isIdentifer(imported)) this.declarateLetSymbol(imported.name, imported.start);
specifiers.push(
Factory.createImportSpecifier(
imported,
null,
cloneSourcePosition(imported.start),
cloneSourcePosition(imported.end),
),
);
continue;
}
specifiers.push(this.parseImportSpecifier());
}
this.expect(SyntaxKinds.BracesRightPunctuator);
}

export function parseImportSpecifier(this: Parser): ImportSpecifier {
const start = this.getStartPosition();
// eslint-disable-next-line prefer-const
let [shouleParseAs, imported, local, isTypeOnly] = this.parseTypePrefixOfSpecifier("import");
if (shouleParseAs && this.isContextKeyword("as")) {
this.nextToken();
const local = this.parseIdentifierReference();
local = this.parseIdentifierReference();
this.declarateLetSymbol(local.name, local.start);
specifiers.push(
Factory.createImportSpecifier(
imported,
local,
cloneSourcePosition(imported.start),
cloneSourcePosition(local.end),
),
return Factory.createImportSpecifier(imported, local, isTypeOnly, start, this.getLastTokenEndPositon());
}
if (isIdentifer(imported) && KeywordSet.has(imported.name)) {
// recoverable error
this.raiseError(ErrorMessageMap.extra_error_unexpect_keyword_in_module_name, imported.start);
} else if (isStringLiteral(imported)) {
// recoverable error
this.raiseError(
ErrorMessageMap.babel_error_string_literal_cannot_be_used_as_an_imported_binding,
imported.start,
);
}
this.expect(SyntaxKinds.BracesRightPunctuator);
if (isIdentifer(imported)) this.declarateLetSymbol(imported.name, imported.start);
return Factory.createImportSpecifier(
imported,
local as Identifier | null,
isTypeOnly,
cloneSourcePosition(imported.start),
cloneSourcePosition(imported.end),
);
}

export function parseTypePrefixOfSpecifier(
this: Parser,
kind: "import" | "export",
): [boolean, StringLiteral | Identifier, Identifier | StringLiteral | null, boolean] {
const maybeType = this.isContextKeyword("type");
let isTypeOnly = false;
let imported = this.parseModuleExportName();
let local: Identifier | StringLiteral | null = null;
let shouleParseAs = true;
// https://github.com/microsoft/TypeScript/blob/fc4f9d83d5939047aa6bb2a43965c6e9bbfbc35b/src/compiler/parser.ts#L7411-L7456
// import { type } from "mod"; - hasTypeSpecifier: false, leftOfAs: type
// import { type as } from "mod"; - hasTypeSpecifier: true, leftOfAs: as
// import { type as as } from "mod"; - hasTypeSpecifier: false, leftOfAs: type, rightOfAs: as
// import { type as as as } from "mod"; - hasTypeSpecifier: true, leftOfAs: as, rightOfAs: as
if (maybeType) {
if (this.isContextKeyword("as")) {
const firstAs = this.parseIdentifierName();
if (this.isContextKeyword("as")) {
const secondAs = this.parseIdentifierName();
if (this.match(IdentiferWithKeyworArray)) {
// type as as <id or keyword>
shouleParseAs = false;
isTypeOnly = true;
imported = firstAs;
local = kind === "import" ? this.parseIdentifierReference() : this.parseModuleExportName();
} else {
// type as as <not id or keyword>
shouleParseAs = false;
local = secondAs;
}
} else if (this.match(IdentiferWithKeyworArray)) {
// type as <id-or-keyword-but-not-`as`>
local = kind === "import" ? this.parseIdentifierReference() : this.parseModuleExportName();
shouleParseAs = false;
} else {
// type as <not id or keyword>
imported = firstAs;
shouleParseAs = false;
isTypeOnly = true;
}
} else if (this.match(IdentiferWithKeyworArray)) {
// type somthing
imported = this.parseIdentifierReference();
isTypeOnly = true;
}
}
return [shouleParseAs, imported, local, isTypeOnly];
}

export function parseImportAttributesOptional(this: Parser): ImportAttribute[] | undefined {
if (
(this.requirePlugin(ParserPlugin.ImportAttribute) && this.match(SyntaxKinds.WithKeyword)) ||
Expand Down Expand Up @@ -455,33 +524,7 @@ export function parseExportNamedDeclaration(this: Parser, start: SourcePosition)
if (this.match(Keywords)) {
isMatchKeyword = true;
}
const exported = this.parseModuleExportName();
if (!this.isVariableDeclarated(helperGetValueOfExportName(exported))) {
undefExportSymbols.push([helperGetValueOfExportName(exported), exported.start]);
}
if (this.isContextKeyword("as")) {
this.nextToken();
const local = this.parseModuleExportName();
this.staticSematicForDuplicateExportName(local);
specifier.push(
Factory.createExportSpecifier(
exported,
local,
cloneSourcePosition(exported.start),
cloneSourcePosition(local.end),
),
);
continue;
}
this.staticSematicForDuplicateExportName(exported);
specifier.push(
Factory.createExportSpecifier(
exported,
null,
cloneSourcePosition(exported.start),
cloneSourcePosition(exported.end),
),
);
specifier.push(this.parseExportSpecifier(undefExportSymbols));
}
const { end: bracesRightPunctuatorEnd } = this.expect(SyntaxKinds.BracesRightPunctuator);
let source: StringLiteral | null = null;
Expand All @@ -507,6 +550,36 @@ export function parseExportNamedDeclaration(this: Parser, start: SourcePosition)
: specifier[specifier.length - 1].end;
return Factory.createExportNamedDeclaration(specifier, null, source, start, cloneSourcePosition(end));
}
export function parseExportSpecifier(
this: Parser,
undefExportSymbols: Array<[string, SourcePosition]>,
): ExportSpecifier {
// eslint-disable-next-line prefer-const
let [shouleParseAs, exported, local, isTypeOnly] = this.parseTypePrefixOfSpecifier("export");
if (!this.isVariableDeclarated(helperGetValueOfExportName(exported))) {
undefExportSymbols.push([helperGetValueOfExportName(exported), exported.start]);
}
if (shouleParseAs && this.isContextKeyword("as")) {
this.nextToken();
local = this.parseModuleExportName();
this.staticSematicForDuplicateExportName(local);
return Factory.createExportSpecifier(
exported,
local,
isTypeOnly,
cloneSourcePosition(exported.start),
cloneSourcePosition(local.end),
);
}
this.staticSematicForDuplicateExportName(exported);
return Factory.createExportSpecifier(
exported,
local,
isTypeOnly,
cloneSourcePosition(exported.start),
cloneSourcePosition(exported.end),
);
}
/**
* Static Sematic Check based on
* - 16.2.3.1 Static Semantics: Early Errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
}
},
"local": null,
"isTypeOnly": false,
"start": {
"row": 3,
"col": 9,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
}
},
"local": null,
"isTypeOnly": false,
"start": {
"row": 1,
"col": 10,
Expand Down
Loading

0 comments on commit 3902b11

Please sign in to comment.