Skip to content

Commit 48fa6e4

Browse files
authored
Simplify parser options passing, share ASTs even more (#1158)
1 parent 0839714 commit 48fa6e4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+583
-602
lines changed

internal/api/encoder/encoder_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ import (
1313
"github.com/microsoft/typescript-go/internal/core"
1414
"github.com/microsoft/typescript-go/internal/parser"
1515
"github.com/microsoft/typescript-go/internal/repo"
16-
"github.com/microsoft/typescript-go/internal/scanner"
1716
"github.com/microsoft/typescript-go/internal/testutil/baseline"
1817
"gotest.tools/v3/assert"
1918
)
2019

21-
var parseCompilerOptions = &core.SourceFileAffectingCompilerOptions{
20+
var parseCompilerOptions = core.SourceFileAffectingCompilerOptions{
2221
EmitScriptTarget: core.ScriptTargetLatest,
2322
}
2423

2524
func TestEncodeSourceFile(t *testing.T) {
2625
t.Parallel()
27-
sourceFile := parser.ParseSourceFile("/test.ts", "/test.ts", "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", parseCompilerOptions, nil, scanner.JSDocParsingModeParseAll)
26+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
27+
FileName: "/test.ts",
28+
Path: "/test.ts",
29+
CompilerOptions: parseCompilerOptions,
30+
}, "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptKindTS)
2831
t.Run("baseline", func(t *testing.T) {
2932
t.Parallel()
3033
buf, err := encoder.EncodeSourceFile(sourceFile, "")
@@ -42,14 +45,11 @@ func BenchmarkEncodeSourceFile(b *testing.B) {
4245
filePath := filepath.Join(repo.TypeScriptSubmodulePath, "src/compiler/checker.ts")
4346
fileContent, err := os.ReadFile(filePath)
4447
assert.NilError(b, err)
45-
sourceFile := parser.ParseSourceFile(
46-
"/checker.ts",
47-
"/checker.ts",
48-
string(fileContent),
49-
parseCompilerOptions,
50-
nil,
51-
scanner.JSDocParsingModeParseAll,
52-
)
48+
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
49+
FileName: "/checker.ts",
50+
Path: "/checker.ts",
51+
CompilerOptions: parseCompilerOptions,
52+
}, string(fileContent), core.ScriptKindTS)
5353

5454
for b.Loop() {
5555
_, err := encoder.EncodeSourceFile(sourceFile, "")

internal/ast/ast.go

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9985,17 +9985,14 @@ type SourceFile struct {
99859985
compositeNodeBase
99869986

99879987
// Fields set by NewSourceFile
9988-
9989-
text string
9990-
fileName string
9991-
path tspath.Path
9992-
Statements *NodeList // NodeList[*Statement]
9988+
fileName string // For debugging convenience
9989+
parseOptions SourceFileParseOptions
9990+
text string
9991+
Statements *NodeList // NodeList[*Statement]
99939992

99949993
// Fields set by parser
9995-
99969994
diagnostics []*Diagnostic
99979995
jsdocDiagnostics []*Diagnostic
9998-
LanguageVersion core.ScriptTarget
99999996
LanguageVariant core.LanguageVariant
100009997
ScriptKind core.ScriptKind
100019998
IsDeclarationFile bool
@@ -10040,30 +10037,36 @@ type SourceFile struct {
1004010037
tokenCache map[core.TextRange]*Node
1004110038
}
1004210039

10043-
func (f *NodeFactory) NewSourceFile(text string, fileName string, path tspath.Path, statements *NodeList) *Node {
10044-
if (tspath.GetEncodedRootLength(fileName) == 0 && !strings.HasPrefix(fileName, "^/")) || fileName != tspath.NormalizePath(fileName) {
10045-
panic(fmt.Sprintf("fileName should be normalized and absolute: %q", fileName))
10040+
func (f *NodeFactory) NewSourceFile(opts SourceFileParseOptions, text string, statements *NodeList) *Node {
10041+
if (tspath.GetEncodedRootLength(opts.FileName) == 0 && !strings.HasPrefix(opts.FileName, "^/")) || opts.FileName != tspath.NormalizePath(opts.FileName) {
10042+
panic(fmt.Sprintf("fileName should be normalized and absolute: %q", opts.FileName))
1004610043
}
10047-
1004810044
data := &SourceFile{}
10045+
data.fileName = opts.FileName
10046+
data.parseOptions = opts
1004910047
data.text = text
10050-
data.fileName = fileName
10051-
data.path = path
1005210048
data.Statements = statements
10053-
data.LanguageVersion = core.ScriptTargetLatest
1005410049
return f.newNode(KindSourceFile, data)
1005510050
}
1005610051

10052+
func (node *SourceFile) ParseOptions() SourceFileParseOptions {
10053+
return node.parseOptions
10054+
}
10055+
10056+
func (node *SourceFile) LanguageVersion() core.ScriptTarget {
10057+
return node.parseOptions.CompilerOptions.EmitScriptTarget
10058+
}
10059+
1005710060
func (node *SourceFile) Text() string {
1005810061
return node.text
1005910062
}
1006010063

1006110064
func (node *SourceFile) FileName() string {
10062-
return node.fileName
10065+
return node.parseOptions.FileName
1006310066
}
1006410067

1006510068
func (node *SourceFile) Path() tspath.Path {
10066-
return node.path
10069+
return node.parseOptions.Path
1006710070
}
1006810071

1006910072
func (node *SourceFile) OriginalFileName() string {
@@ -10120,7 +10123,6 @@ func (node *SourceFile) IsJS() bool {
1012010123

1012110124
func (node *SourceFile) copyFrom(other *SourceFile) {
1012210125
// Do not copy fields set by NewSourceFile (Text, FileName, Path, or Statements)
10123-
node.LanguageVersion = other.LanguageVersion
1012410126
node.LanguageVariant = other.LanguageVariant
1012510127
node.ScriptKind = other.ScriptKind
1012610128
node.IsDeclarationFile = other.IsDeclarationFile
@@ -10141,7 +10143,7 @@ func (node *SourceFile) copyFrom(other *SourceFile) {
1014110143
}
1014210144

1014310145
func (node *SourceFile) Clone(f NodeFactoryCoercible) *Node {
10144-
updated := f.AsNodeFactory().NewSourceFile(node.Text(), node.FileName(), node.Path(), node.Statements)
10146+
updated := f.AsNodeFactory().NewSourceFile(node.parseOptions, node.text, node.Statements)
1014510147
newFile := updated.AsSourceFile()
1014610148
newFile.copyFrom(node)
1014710149
return cloneNode(updated, node.AsNode(), f.AsNodeFactory().hooks)
@@ -10153,7 +10155,7 @@ func (node *SourceFile) computeSubtreeFacts() SubtreeFacts {
1015310155

1015410156
func (f *NodeFactory) UpdateSourceFile(node *SourceFile, statements *StatementList) *Node {
1015510157
if statements != node.Statements {
10156-
updated := f.NewSourceFile(node.Text(), node.fileName, node.path, statements).AsSourceFile()
10158+
updated := f.NewSourceFile(node.parseOptions, node.text, statements).AsSourceFile()
1015710159
updated.copyFrom(node)
1015810160
return updateNode(updated.AsNode(), node.AsNode(), f.hooks)
1015910161
}

internal/ast/parseoptions.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package ast
2+
3+
import (
4+
"github.com/microsoft/typescript-go/internal/core"
5+
"github.com/microsoft/typescript-go/internal/tspath"
6+
)
7+
8+
type JSDocParsingMode int
9+
10+
const (
11+
JSDocParsingModeParseAll JSDocParsingMode = iota
12+
JSDocParsingModeParseNone
13+
JSDocParsingModeParseForTypeErrors
14+
JSDocParsingModeParseForTypeInfo
15+
)
16+
17+
type SourceFileParseOptions struct {
18+
FileName string
19+
Path tspath.Path
20+
CompilerOptions core.SourceFileAffectingCompilerOptions
21+
ExternalModuleIndicatorOptions ExternalModuleIndicatorOptions
22+
JSDocParsingMode JSDocParsingMode
23+
}
24+
25+
func GetSourceFileAffectingCompilerOptions(fileName string, options *core.CompilerOptions) core.SourceFileAffectingCompilerOptions {
26+
// Declaration files are not parsed/bound differently depending on compiler options.
27+
if tspath.IsDeclarationFileName(fileName) {
28+
return core.SourceFileAffectingCompilerOptions{}
29+
}
30+
return options.SourceFileAffecting()
31+
}
32+
33+
type ExternalModuleIndicatorOptions struct {
34+
jsx bool
35+
force bool
36+
}
37+
38+
func GetExternalModuleIndicatorOptions(fileName string, options *core.CompilerOptions, metadata SourceFileMetaData) ExternalModuleIndicatorOptions {
39+
if tspath.IsDeclarationFileName(fileName) {
40+
return ExternalModuleIndicatorOptions{}
41+
}
42+
43+
switch options.GetEmitModuleDetectionKind() {
44+
case core.ModuleDetectionKindForce:
45+
// All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
46+
return ExternalModuleIndicatorOptions{force: true}
47+
case core.ModuleDetectionKindLegacy:
48+
// Files are modules if they have imports, exports, or import.meta
49+
return ExternalModuleIndicatorOptions{}
50+
case core.ModuleDetectionKindAuto:
51+
// If module is nodenext or node16, all esm format files are modules
52+
// If jsx is react-jsx or react-jsxdev then jsx tags force module-ness
53+
// otherwise, the presence of import or export statments (or import.meta) implies module-ness
54+
return ExternalModuleIndicatorOptions{
55+
jsx: options.Jsx == core.JsxEmitReactJSX || options.Jsx == core.JsxEmitReactJSXDev,
56+
force: isFileForcedToBeModuleByFormat(fileName, options, metadata),
57+
}
58+
default:
59+
return ExternalModuleIndicatorOptions{}
60+
}
61+
}
62+
63+
var isFileForcedToBeModuleByFormatExtensions = []string{tspath.ExtensionCjs, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionMts}
64+
65+
func isFileForcedToBeModuleByFormat(fileName string, options *core.CompilerOptions, metadata SourceFileMetaData) bool {
66+
// Excludes declaration files - they still require an explicit `export {}` or the like
67+
// for back compat purposes. The only non-declaration files _not_ forced to be a module are `.js` files
68+
// that aren't esm-mode (meaning not in a `type: module` scope).
69+
if GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), metadata) == core.ModuleKindESNext || tspath.FileExtensionIsOneOf(fileName, isFileForcedToBeModuleByFormatExtensions) {
70+
return true
71+
}
72+
return false
73+
}
74+
75+
func SetExternalModuleIndicator(file *SourceFile, opts ExternalModuleIndicatorOptions) {
76+
file.ExternalModuleIndicator = getExternalModuleIndicator(file, opts)
77+
}
78+
79+
func getExternalModuleIndicator(file *SourceFile, opts ExternalModuleIndicatorOptions) *Node {
80+
if file.ScriptKind == core.ScriptKindJSON {
81+
return nil
82+
}
83+
84+
if node := isFileProbablyExternalModule(file); node != nil {
85+
return node
86+
}
87+
88+
if file.IsDeclarationFile {
89+
return nil
90+
}
91+
92+
if opts.jsx {
93+
if node := isFileModuleFromUsingJSXTag(file); node != nil {
94+
return node
95+
}
96+
}
97+
98+
if opts.force {
99+
return file.AsNode()
100+
}
101+
102+
return nil
103+
}
104+
105+
func isFileProbablyExternalModule(sourceFile *SourceFile) *Node {
106+
for _, statement := range sourceFile.Statements.Nodes {
107+
if IsExternalModuleIndicator(statement) {
108+
return statement
109+
}
110+
}
111+
return getImportMetaIfNecessary(sourceFile)
112+
}
113+
114+
func getImportMetaIfNecessary(sourceFile *SourceFile) *Node {
115+
if sourceFile.AsNode().Flags&NodeFlagsPossiblyContainsImportMeta != 0 {
116+
return findChildNode(sourceFile.AsNode(), IsImportMeta)
117+
}
118+
return nil
119+
}
120+
121+
func findChildNode(root *Node, check func(*Node) bool) *Node {
122+
var result *Node
123+
var visit func(*Node) bool
124+
visit = func(node *Node) bool {
125+
if check(node) {
126+
result = node
127+
return true
128+
}
129+
return node.ForEachChild(visit)
130+
}
131+
visit(root)
132+
return result
133+
}
134+
135+
func isFileModuleFromUsingJSXTag(file *SourceFile) *Node {
136+
return walkTreeForJSXTags(file.AsNode())
137+
}
138+
139+
// This is a somewhat unavoidable full tree walk to locate a JSX tag - `import.meta` requires the same,
140+
// but we avoid that walk (or parts of it) if at all possible using the `PossiblyContainsImportMeta` node flag.
141+
// Unfortunately, there's no `NodeFlag` space to do the same for JSX.
142+
func walkTreeForJSXTags(node *Node) *Node {
143+
var found *Node
144+
145+
var visitor func(node *Node) bool
146+
visitor = func(node *Node) bool {
147+
if found != nil {
148+
return true
149+
}
150+
if node.SubtreeFacts()&SubtreeContainsJsx == 0 {
151+
return false
152+
}
153+
if IsJsxOpeningElement(node) || IsJsxFragment(node) {
154+
found = node
155+
return true
156+
}
157+
return node.ForEachChild(visitor)
158+
}
159+
visitor(node)
160+
161+
return found
162+
}

internal/ast/utilities.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,27 +2549,24 @@ func GetImpliedNodeFormatForFile(path string, packageJsonType string) core.Modul
25492549
return impliedNodeFormat
25502550
}
25512551

2552-
func GetEmitModuleFormatOfFileWorker(fileName string, options *core.CompilerOptions, sourceFileMetaData *SourceFileMetaData) core.ModuleKind {
2552+
func GetEmitModuleFormatOfFileWorker(fileName string, options *core.CompilerOptions, sourceFileMetaData SourceFileMetaData) core.ModuleKind {
25532553
result := GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), sourceFileMetaData)
25542554
if result != core.ModuleKindNone {
25552555
return result
25562556
}
25572557
return options.GetEmitModuleKind()
25582558
}
25592559

2560-
func GetImpliedNodeFormatForEmitWorker(fileName string, emitModuleKind core.ModuleKind, sourceFileMetaData *SourceFileMetaData) core.ResolutionMode {
2560+
func GetImpliedNodeFormatForEmitWorker(fileName string, emitModuleKind core.ModuleKind, sourceFileMetaData SourceFileMetaData) core.ResolutionMode {
25612561
if core.ModuleKindNode16 <= emitModuleKind && emitModuleKind <= core.ModuleKindNodeNext {
2562-
if sourceFileMetaData == nil {
2563-
return core.ModuleKindNone
2564-
}
25652562
return sourceFileMetaData.ImpliedNodeFormat
25662563
}
2567-
if sourceFileMetaData != nil && sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindCommonJS &&
2564+
if sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindCommonJS &&
25682565
(sourceFileMetaData.PackageJsonType == "commonjs" ||
25692566
tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionCjs, tspath.ExtensionCts})) {
25702567
return core.ModuleKindCommonJS
25712568
}
2572-
if sourceFileMetaData != nil && sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindESNext &&
2569+
if sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindESNext &&
25732570
(sourceFileMetaData.PackageJsonType == "module" ||
25742571
tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMjs, tspath.ExtensionMts})) {
25752572
return core.ModuleKindESNext
@@ -2899,15 +2896,6 @@ func GetClassLikeDeclarationOfSymbol(symbol *Symbol) *Node {
28992896
return core.Find(symbol.Declarations, IsClassLike)
29002897
}
29012898

2902-
func GetLanguageVariant(scriptKind core.ScriptKind) core.LanguageVariant {
2903-
switch scriptKind {
2904-
case core.ScriptKindTSX, core.ScriptKindJSX, core.ScriptKindJS, core.ScriptKindJSON:
2905-
// .tsx and .jsx files are treated as jsx language variant.
2906-
return core.LanguageVariantJSX
2907-
}
2908-
return core.LanguageVariantStandard
2909-
}
2910-
29112899
func IsCallLikeExpression(node *Node) bool {
29122900
switch node.Kind {
29132901
case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxOpeningFragment, KindCallExpression, KindNewExpression,

0 commit comments

Comments
 (0)