vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
package parser_test
import (
"io/fs"
"iter"
"os"
"path/filepath"
"strings"
"testing"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/parser"
"github.com/microsoft/typescript-go/internal/repo"
"github.com/microsoft/typescript-go/internal/testrunner"
"github.com/microsoft/typescript-go/internal/testutil/fixtures"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
"gotest.tools/v3/assert"
)
func BenchmarkParse(b *testing.B) {
for _, f := range fixtures.BenchFixtures {
b.Run(f.Name(), func(b *testing.B) {
f.SkipIfNotExist(b)
fileName := tspath.GetNormalizedAbsolutePath(f.Path(), "/")
path := tspath.ToPath(fileName, "/", osvfs.FS().UseCaseSensitiveFileNames())
sourceText := f.ReadFile(b)
scriptKind := core.GetScriptKindFromFileName(fileName)
opts := ast.SourceFileParseOptions{
FileName: fileName,
Path: path,
}
for b.Loop() {
parser.ParseSourceFile(opts, sourceText, scriptKind)
}
})
}
}
type parsableFile struct {
path string
name string
}
func allParsableFiles(tb testing.TB, root string) iter.Seq[parsableFile] {
tb.Helper()
return func(yield func(parsableFile) bool) {
tb.Helper()
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || tspath.TryGetExtensionFromPath(path) == "" {
return nil
}
testName, err := filepath.Rel(root, path)
if err != nil {
return err
}
testName = filepath.ToSlash(testName)
if !yield(parsableFile{path, testName}) {
return filepath.SkipAll
}
return nil
})
assert.NilError(tb, err)
}
}
func FuzzParser(f *testing.F) {
repo.SkipIfNoTypeScriptSubmodule(f)
tests := []string{
"src",
"scripts",
"Herebyfile.mjs",
}
var extensions collections.Set[string]
for _, es := range tspath.AllSupportedExtensionsWithJson {
for _, e := range es {
extensions.Add(e)
}
}
for _, test := range tests {
root := filepath.Join(repo.TypeScriptSubmodulePath(), test)
for file := range allParsableFiles(f, root) {
sourceText, err := os.ReadFile(file.path)
assert.NilError(f, err)
extension := tspath.TryGetExtensionFromPath(file.path)
f.Add(extension, string(sourceText), false, false)
}
}
testDirs := []string{
filepath.Join(repo.TypeScriptSubmodulePath(), "tests/cases/compiler"),
filepath.Join(repo.TypeScriptSubmodulePath(), "tests/cases/conformance"),
filepath.Join(repo.TestDataPath(), "tests/cases/compiler"),
}
for _, testDir := range testDirs {
if _, err := os.Stat(testDir); os.IsNotExist(err) {
continue
}
for file := range allParsableFiles(f, testDir) {
sourceText, err := os.ReadFile(file.path)
assert.NilError(f, err)
type testFile struct {
content string
name string
}
testUnits, _, _, _, err := testrunner.ParseTestFilesAndSymlinks(
string(sourceText),
file.path,
func(filename string, content string, fileOptions map[string]string) (testFile, error) {
return testFile{content: content, name: filename}, nil
},
)
assert.NilError(f, err)
for _, unit := range testUnits {
extension := tspath.TryGetExtensionFromPath(unit.name)
if extension == "" {
continue
}
f.Add(extension, unit.content, false, false)
}
}
}
f.Fuzz(func(t *testing.T, extension string, sourceText string, externalModuleIndicatorOptionsJSX bool, externalModuleIndicatorOptionsForce bool) {
if !extensions.Has(extension) {
t.Skip()
}
fileName := "/index" + extension
path := tspath.Path(fileName)
opts := ast.SourceFileParseOptions{
FileName: fileName,
Path: path,
ExternalModuleIndicatorOptions: ast.ExternalModuleIndicatorOptions{
JSX: externalModuleIndicatorOptionsJSX,
Force: externalModuleIndicatorOptionsForce,
},
}
parser.ParseSourceFile(opts, sourceText, core.GetScriptKindFromFileName(fileName))
})
}
func TestJSDocImportTypeParentChain(t *testing.T) {
t.Parallel()
sourceText := `test("", async function () {
;(/** @type {typeof import("a")} */ ({}))
})
test("", async function () {
;(/** @type {typeof import("a")} */ a)
})
test("", async function () {
(/** @type {typeof import("a")} */ ({}))
;(/** @type {typeof import("a")} */ ({}))
})
test("", async function () {
(/** @type {typeof import("a")} */ a)
;(/** @type {typeof import("a")} */ a)
})
test("", async function () {
(/** @type {typeof import("a")} */ ({}))
;(/** @type {typeof import("a")} */ ({}))
})
`
opts := ast.SourceFileParseOptions{
FileName: "/index.js",
Path: "/index.js",
}
file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS)
for i := 1; i < len(file.ReparsedClones); i++ {
a, b := file.ReparsedClones[i-1], file.ReparsedClones[i]
if a.Pos() == b.Pos() && a.End() == b.End() && a.Kind == b.Kind {
t.Errorf("duplicate ReparsedClones at [%d] and [%d]: %s pos=%d end=%d", i-1, i, a.Kind.String(), a.Pos(), a.End())
}
}
for _, imp := range file.Imports() {
reparsed := ast.GetReparsedNodeForNode(imp)
if ast.GetSourceFileOfNode(reparsed) == nil {
t.Errorf("reparsed import at pos=%d has broken parent chain", imp.Pos())
}
}
}
func TestSourceFileContainsNonASCIIInStringLiteralFastPath(t *testing.T) {
t.Parallel()
sourceText := `const x = "─";
namespace N {
export const y = x;
}
`
opts := ast.SourceFileParseOptions{
FileName: "/index.ts",
Path: "/index.ts",
}
file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindTS)
assert.Assert(t, file.ContainsNonASCII)
positionMap := file.GetPositionMap()
assert.Assert(t, !positionMap.IsAsciiOnly())
afterBoxDrawingCharacter := strings.Index(sourceText, "─") + len("─")
assert.Equal(t, positionMap.UTF8ToUTF16(afterBoxDrawingCharacter), afterBoxDrawingCharacter-2)
assert.Equal(t, positionMap.UTF8ToUTF16(len(sourceText)), len(sourceText)-2)
}

View File

@@ -0,0 +1,71 @@
package parser
import (
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/tspath"
)
func collectExternalModuleReferences(file *ast.SourceFile) {
for _, node := range file.Statements.Nodes {
collectModuleReferences(file, node, false /*inAmbientModule*/)
}
if file.Flags&ast.NodeFlagsPossiblyContainsDynamicImport != 0 || ast.IsInJSFile(file.AsNode()) {
ast.ForEachDynamicImportOrRequireCall(file /*includeTypeSpaceImports*/, true /*requireStringLiteralLikeArgument*/, true, func(node *ast.Node, moduleSpecifier *ast.Expression) bool {
ast.SetImportsOfSourceFile(file, append(file.Imports(), moduleSpecifier))
return false
})
}
}
func collectModuleReferences(file *ast.SourceFile, node *ast.Statement, inAmbientModule bool) {
if ast.IsAnyImportOrReExport(node) {
moduleNameExpr := ast.GetExternalModuleName(node)
// TypeScript 1.0 spec (April 2014): 12.1.6
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules
// only through top - level external module names. Relative external module names are not permitted.
if moduleNameExpr != nil && ast.IsStringLiteral(moduleNameExpr) {
moduleName := moduleNameExpr.Text()
if moduleName != "" && (!inAmbientModule || !tspath.IsExternalModuleNameRelative(moduleName)) {
ast.SetImportsOfSourceFile(file, append(file.Imports(), moduleNameExpr))
// !!! removed `&& p.currentNodeModulesDepth == 0`
if file.UsesUriStyleNodeCoreModules != core.TSTrue && !file.IsDeclarationFile {
if strings.HasPrefix(moduleName, "node:") && !core.ExclusivelyPrefixedNodeCoreModules[moduleName] {
// Presence of `node:` prefix takes precedence over unprefixed node core modules
file.UsesUriStyleNodeCoreModules = core.TSTrue
} else if file.UsesUriStyleNodeCoreModules == core.TSUnknown && core.UnprefixedNodeCoreModules[moduleName] {
// Avoid `unprefixedNodeCoreModules.has` for every import
file.UsesUriStyleNodeCoreModules = core.TSFalse
}
}
}
}
return
}
if ast.IsModuleDeclaration(node) && ast.IsAmbientModule(node) && (inAmbientModule || ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) || file.IsDeclarationFile) {
nameText := node.AsModuleDeclaration().Name().Text()
// Ambient module declarations can be interpreted as augmentations for some existing external modules.
// This will happen in two cases:
// - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope
// - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name
// immediately nested in top level ambient module declaration .
if ast.IsExternalModule(file) || (inAmbientModule && !tspath.IsExternalModuleNameRelative(nameText)) {
file.ModuleAugmentations = append(file.ModuleAugmentations, node.AsModuleDeclaration().Name())
} else if !inAmbientModule {
file.AmbientModuleNames = append(file.AmbientModuleNames, nameText)
// An AmbientExternalModuleDeclaration declares an external module.
// This type of declaration is permitted only in the global module.
// The StringLiteral must specify a top - level external module name.
// Relative external module names are not permitted
// NOTE: body of ambient module is always a module block, if it exists
if node.Body() != nil {
for _, statement := range node.Body().Statements() {
collectModuleReferences(file, statement, true /*inAmbientModule*/)
}
}
}
}
}

View File

@@ -0,0 +1,748 @@
package parser
import (
"strconv"
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/scanner"
)
func (p *Parser) finishReparsedNode(node *ast.Node, locationNode *ast.Node) {
node.Flags = p.contextFlags | ast.NodeFlagsReparsed
node.Loc = locationNode.Loc
p.overrideParentInImmediateChildren(node)
}
func (p *Parser) finishMutatedNode(node *ast.Node) {
p.overrideParentInImmediateChildren(node)
}
// Deep-clone the given node and add the clone to the reparsed clone list. The list is used by ast.GetReparsedNodeForNode
// to locate reparsed clones of JSDoc nodes. Since the binder attaches symbols to reparsed nodes and not to JSDoc nodes, we
// need the mapping when obtaining symbols and types from JSDoc nodes.
func (p *Parser) addDeepCloneReparse(node *ast.Node) *ast.Node {
clone := p.factory.DeepCloneReparse(node)
if clone != nil {
p.reparsedClones = append(p.reparsedClones, clone)
}
return clone
}
func (p *Parser) addTransformedReparse(newNode *ast.Node, old *ast.Node) *ast.Node {
p.finishReparsedNode(newNode, old)
newNode.Flags |= ast.NodeFlagsReparserTransformedLiteral
p.reparsedClones = append(p.reparsedClones, newNode)
return newNode
}
func (p *Parser) checkNonIdentifierName(name *ast.Node) *ast.Node {
if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) {
errLoc := name.Loc
if errLoc.Len() == 0 { // missing name, emit error on the character before the missing name node
errLoc = core.NewTextRange(name.Loc.Pos()-1, name.Loc.Pos())
}
p.parseErrorAtRange(errLoc, diagnostics.Identifier_expected)
}
return name
}
// Hosted tags find a host and add their children to the correct location under the host.
// Unhosted tags add synthetic nodes to the reparse list.
func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
for _, j := range jsDoc {
isLast := j == jsDoc[len(jsDoc)-1]
tags := j.AsJSDoc().Tags
if tags == nil {
continue
}
for _, tag := range tags.Nodes {
p.reparseUnhosted(tag, parent, j)
if isLast {
p.reparseHosted(tag, parent, j)
}
}
}
}
func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) {
switch tag.Kind {
case ast.KindJSDocTypedefTag:
typeExpression := tag.TypeExpression()
if typeExpression == nil {
break
}
fullName := tag.Name()
isNamespace := fullName != nil && ast.IsModuleDeclaration(fullName)
var modifiers *ast.ModifierList
if isNamespace {
modifiers = p.createExportModifier(tag)
}
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.checkNonIdentifierName(p.getInnermostNameOfJSDocNamespace(fullName))), nil, nil)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
var t *ast.Node
switch typeExpression.Kind {
case ast.KindJSDocTypeExpression:
t = p.addDeepCloneReparse(typeExpression.Type())
case ast.KindJSDocTypeLiteral:
t = p.reparseJSDocTypeLiteral(typeExpression)
default:
panic("typedef tag type expression should be a name reference or a type expression" + typeExpression.Kind.String())
}
typeAlias.AsTypeAliasDeclaration().Type = t
p.finishReparsedNode(typeAlias, tag)
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
result := p.wrapInJSDocNamespace(fullName, typeAlias, false /*nested*/)
p.reparseList = append(p.reparseList, result)
case ast.KindJSDocCallbackTag:
typeExpression := tag.TypeExpression()
if typeExpression == nil {
break
}
fullName := tag.Name()
isNamespace := fullName != nil && ast.IsModuleDeclaration(fullName)
var modifiers *ast.ModifierList
if isNamespace {
modifiers = p.createExportModifier(tag)
}
functionType := p.reparseJSDocSignature(typeExpression, tag, jsDoc, tag, nil)
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.getInnermostNameOfJSDocNamespace(fullName)), nil, functionType)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
p.finishReparsedNode(typeAlias, tag)
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
result := p.wrapInJSDocNamespace(fullName, typeAlias, false /*nested*/)
p.reparseList = append(p.reparseList, result)
case ast.KindJSDocImportTag:
importTag := tag.AsJSDocImportTag()
if importTag.ImportClause == nil {
break
}
importClause := p.addDeepCloneReparse(importTag.ImportClause)
importClause.AsImportClause().PhaseModifier = ast.KindTypeKeyword
importDeclaration := p.factory.NewJSImportDeclaration(
p.factory.DeepCloneReparseModifiers(importTag.Modifiers()),
importClause,
p.addDeepCloneReparse(importTag.ModuleSpecifier),
p.addDeepCloneReparse(importTag.Attributes),
)
p.finishReparsedNode(importDeclaration, tag)
p.reparseList = append(p.reparseList, importDeclaration)
case ast.KindJSDocOverloadTag:
// Create overload signatures only for function, method, and constructor declarations outside object literals
if (ast.IsFunctionDeclaration(parent) || ast.IsMethodDeclaration(parent) || ast.IsConstructorDeclaration(parent)) && p.parsingContexts&(1<<PCObjectLiteralMembers) == 0 {
p.reparseList = append(p.reparseList, p.reparseJSDocSignature(tag.AsJSDocOverloadTag().TypeExpression, parent, jsDoc, tag, parent.Modifiers()))
}
}
}
func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsDoc *ast.Node, tag *ast.Node, modifiers *ast.ModifierList) *ast.Node {
var signature *ast.Node
clonedModifiers := p.factory.DeepCloneReparseModifiers(modifiers)
switch fun.Kind {
case ast.KindFunctionDeclaration:
signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil)
case ast.KindMethodDeclaration:
signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil, nil)
case ast.KindConstructor:
signature = p.factory.NewConstructorDeclaration(clonedModifiers, nil, nil, nil, nil, nil)
case ast.KindJSDocCallbackTag:
signature = p.factory.NewFunctionTypeNode(nil, nil, p.factory.NewKeywordTypeNode(ast.KindAnyKeyword))
default:
panic("Unexpected kind " + fun.Kind.String())
}
if tag.Kind != ast.KindJSDocCallbackTag {
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
}
parameters := p.nodeSliceArena.NewSlice(0)
for pi, param := range jsSignature.Parameters() {
var parameter *ast.Node
if param.Kind == ast.KindJSDocThisTag {
thisTag := param.AsJSDocThisTag()
thisIdent := p.factory.NewIdentifier("this")
thisIdent.Loc = thisTag.Loc
thisIdent.Flags = p.contextFlags | ast.NodeFlagsReparsed
parameter = p.factory.NewParameterDeclaration(nil, nil, thisIdent, nil, nil, nil)
if thisTag.TypeExpression != nil {
parameter.AsParameterDeclaration().Type = p.addDeepCloneReparse(thisTag.TypeExpression.Type())
}
} else if param.Kind == ast.KindJSDocParameterTag || param.Kind == ast.KindJSDocPropertyTag {
jsparam := param.AsJSDocParameterOrPropertyTag()
// Skip sub-property parameters (e.g., @param x.y) - these have QualifiedNames
// and describe properties of a parent parameter, not standalone parameters.
if ast.IsQualifiedName(jsparam.Name()) {
continue
}
var dotDotDotToken *ast.Node
var paramType *ast.TypeNode
if jsparam.TypeExpression != nil {
if jsparam.TypeExpression.Type().Kind == ast.KindJSDocVariadicType {
dotDotDotToken = p.factory.NewToken(ast.KindDotDotDotToken)
dotDotDotToken.Loc = jsparam.Loc
dotDotDotToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
variadicType := jsparam.TypeExpression.Type().AsJSDocVariadicType()
paramType = p.reparseJSDocTypeLiteral(variadicType.Type)
} else {
paramType = p.reparseJSDocTypeLiteral(jsparam.TypeExpression.Type())
}
}
name := jsparam.Name()
if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) {
// drop invalid chars for _, if empty, write _0, etc., so we have a valid param name to emit later
result := strings.Builder{}
for i, ch := range name.AsIdentifier().Text {
if i == 0 {
if !scanner.IsIdentifierStart(ch) {
result.WriteRune('_')
} else {
result.WriteRune(ch)
}
continue
} else if !scanner.IsIdentifierPart(ch) {
result.WriteRune('_')
} else {
result.WriteRune(ch)
}
}
if result.Len() == 0 {
result.WriteRune('_')
result.WriteString(strconv.Itoa(pi))
}
name = p.addTransformedReparse(p.factory.NewIdentifier(result.String()), name)
} else {
name = p.addDeepCloneReparse(name)
}
parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, name, p.makeQuestionIfOptional(jsparam), paramType, nil)
}
p.finishReparsedNode(parameter, param)
parameters = append(parameters, parameter)
p.reparseJSDocComment(parameter, param)
}
signature.FunctionLikeData().Parameters = p.newNodeList(jsSignature.AsJSDocSignature().Parameters.Loc, parameters)
if jsSignature.Type() != nil && jsSignature.Type().TypeExpression() != nil {
signature.FunctionLikeData().Type = p.addDeepCloneReparse(jsSignature.Type().TypeExpression().Type())
}
loc := jsSignature
if tag.Kind == ast.KindJSDocOverloadTag {
loc = tag.TagName()
}
p.finishReparsedNode(signature, loc)
return signature
}
func (p *Parser) reparseJSDocTypeLiteral(t *ast.TypeNode) *ast.Node {
if t == nil {
return nil
}
if t.Kind == ast.KindJSDocTypeLiteral {
jstypeliteral := t.AsJSDocTypeLiteral()
isArrayType := jstypeliteral.IsArrayType
properties := p.nodeSliceArena.NewSlice(0)
for _, prop := range jstypeliteral.JSDocPropertyTags {
if prop.Kind != ast.KindJSDocPropertyTag && prop.Kind != ast.KindJSDocParameterTag {
continue
}
jsprop := prop.AsJSDocParameterOrPropertyTag()
name := prop.Name()
if name.Kind == ast.KindQualifiedName {
name = name.AsQualifiedName().Right
}
if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) {
name = p.addTransformedReparse(p.factory.NewStringLiteral(name.AsIdentifier().Text, ast.TokenFlagsNone), name)
} else {
name = p.addDeepCloneReparse(name)
}
property := p.factory.NewPropertySignatureDeclaration(nil, name, p.makeQuestionIfOptional(jsprop), nil, nil)
if jsprop.TypeExpression != nil {
property.AsPropertySignatureDeclaration().Type = p.reparseJSDocTypeLiteral(jsprop.TypeExpression.Type())
}
p.finishReparsedNode(property, prop)
properties = append(properties, property)
p.reparseJSDocComment(property, prop)
}
t = p.factory.NewTypeLiteralNode(p.newNodeList(jstypeliteral.Loc, properties))
if isArrayType {
p.finishReparsedNode(t, jstypeliteral.AsNode())
t = p.factory.NewArrayTypeNode(t)
}
p.finishReparsedNode(t, jstypeliteral.AsNode())
return t
}
return p.addDeepCloneReparse(t)
}
func (p *Parser) reparseJSDocComment(node *ast.Node, tag *ast.Node) {
if comment := tag.CommentList(); comment != nil {
newComment := p.factory.NewNodeList(core.Map(comment.Nodes, p.factory.DeepCloneReparse))
newComment.Loc = comment.Loc
propJSDoc := p.factory.NewJSDoc(newComment, nil)
p.finishReparsedNode(propJSDoc, tag)
propJSDoc.Parent = node
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: node, jsDocs: []*ast.Node{propJSDoc}})
node.Flags |= ast.NodeFlagsHasJSDoc
}
}
func (p *Parser) gatherTypeParameters(j *ast.Node, typedefOrCallback bool) *ast.NodeList {
var typeParameters []*ast.Node
pos := -1
endPos := -1
firstTemplate := true
for _, tag := range j.AsJSDoc().Tags.Nodes {
// When a JSDoc comment contains an `@typedef` or `@callback` tag, `@template` type parameter
// declarations apply to the type being defined.
if !typedefOrCallback && (ast.IsJSDocTypedefTag(tag) || ast.IsJSDocCallbackTag(tag)) {
return nil
}
if !ast.IsJSDocTemplateTag(tag) {
continue
}
if firstTemplate {
pos = tag.Pos()
firstTemplate = false
}
endPos = tag.End()
constraint := tag.AsJSDocTemplateTag().Constraint
firstTypeParameter := true
for _, tp := range tag.TypeParameters() {
var reparse *ast.Node
if constraint != nil && firstTypeParameter {
reparse = p.factory.NewTypeParameterDeclaration(
p.factory.DeepCloneReparseModifiers(tp.Modifiers()),
p.addDeepCloneReparse(p.checkNonIdentifierName(tp.Name())),
p.addDeepCloneReparse(constraint.Type()),
nil, // expression
p.addDeepCloneReparse(tp.AsTypeParameterDeclaration().DefaultType),
)
p.finishReparsedNode(reparse, tp)
} else {
reparse = p.addDeepCloneReparse(tp)
}
if typeParameters == nil {
typeParameters = p.nodeSliceArena.NewSlice(0)
}
typeParameters = append(typeParameters, reparse)
firstTypeParameter = false
}
}
if len(typeParameters) == 0 {
return nil
} else {
return p.newNodeList(core.NewTextRange(pos, endPos), typeParameters)
}
}
func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) {
switch tag.Kind {
case ast.KindJSDocTypeTag:
switch parent.Kind {
case ast.KindVariableStatement:
if parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.Type() == nil && tag.TypeExpression() != nil {
declaration.AsMutable().SetType(p.addDeepCloneReparse(tag.TypeExpression().Type()))
p.finishMutatedNode(declaration)
return
}
}
}
case ast.KindVariableDeclaration, ast.KindExportAssignment, ast.KindPropertyDeclaration, ast.KindPropertyAssignment,
ast.KindShorthandPropertyAssignment, ast.KindGetAccessor:
if parent.Type() == nil && tag.TypeExpression() != nil {
parent.AsMutable().SetType(p.addDeepCloneReparse(tag.TypeExpression().Type()))
p.finishMutatedNode(parent)
return
}
case ast.KindParameter:
if parent.Type() == nil && tag.TypeExpression() != nil {
parent.AsMutable().SetType(p.reparseJSDocTypeLiteral(tag.TypeExpression().Type()))
p.finishMutatedNode(parent)
return
}
case ast.KindExpressionStatement:
if parent.Expression().Kind == ast.KindBinaryExpression {
bin := parent.Expression().AsBinaryExpression()
if kind := ast.GetAssignmentDeclarationKind(bin.AsNode()); kind != ast.JSDeclarationKindNone && tag.TypeExpression() != nil {
bin.AsMutable().SetType(p.addDeepCloneReparse(tag.TypeExpression().Type()))
p.finishMutatedNode(bin.AsNode())
return
}
}
case ast.KindReturnStatement, ast.KindParenthesizedExpression:
if parent.Expression() != nil && tag.TypeExpression() != nil {
parent.AsMutable().SetExpression(p.makeNewCast(
p.addDeepCloneReparse(tag.TypeExpression().Type()),
parent.Expression(),
true, /*isAssertion*/
))
p.finishMutatedNode(parent)
return
}
}
if fun := getFunctionLikeHost(parent); fun != nil {
noTypedParams := core.Every(fun.Parameters(), func(param *ast.Node) bool { return param.Type() == nil })
if fun.TypeParameterList() == nil && fun.Type() == nil && noTypedParams && tag.TypeExpression() != nil {
fun.FunctionLikeData().FullSignature = p.addDeepCloneReparse(tag.TypeExpression().Type())
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocSatisfiesTag:
switch parent.Kind {
case ast.KindVariableStatement:
if parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.Initializer() != nil && tag.TypeExpression() != nil {
declaration.AsMutable().SetInitializer(p.makeNewCast(
p.addDeepCloneReparse(tag.TypeExpression().Type()),
declaration.Initializer(),
false, /*isAssertion*/
))
p.finishMutatedNode(declaration)
break
}
}
}
case ast.KindVariableDeclaration, ast.KindPropertyDeclaration, ast.KindPropertyAssignment:
if parent.Initializer() != nil && tag.TypeExpression() != nil {
parent.AsMutable().SetInitializer(p.makeNewCast(
p.addDeepCloneReparse(tag.TypeExpression().Type()),
parent.Initializer(),
false, /*isAssertion*/
))
p.finishMutatedNode(parent)
}
case ast.KindShorthandPropertyAssignment:
shorthand := parent.AsShorthandPropertyAssignment()
if shorthand.ObjectAssignmentInitializer != nil && tag.AsJSDocSatisfiesTag().TypeExpression != nil {
shorthand.ObjectAssignmentInitializer = p.makeNewCast(
p.addDeepCloneReparse(tag.AsJSDocSatisfiesTag().TypeExpression.Type()),
shorthand.ObjectAssignmentInitializer,
false, /*isAssertion*/
)
p.finishMutatedNode(parent)
}
case ast.KindReturnStatement, ast.KindParenthesizedExpression, ast.KindExportAssignment:
if parent.Expression() != nil && tag.TypeExpression() != nil {
parent.AsMutable().SetExpression(p.makeNewCast(
p.addDeepCloneReparse(tag.TypeExpression().Type()),
parent.Expression(),
false, /*isAssertion*/
))
p.finishMutatedNode(parent)
}
case ast.KindExpressionStatement:
if parent.Expression().Kind == ast.KindBinaryExpression {
bin := parent.Expression().AsBinaryExpression()
if kind := ast.GetAssignmentDeclarationKind(bin.AsNode()); kind != ast.JSDeclarationKindNone && tag.TypeExpression() != nil {
bin.Right = p.makeNewCast(
p.addDeepCloneReparse(tag.TypeExpression().Type()),
bin.Right,
false, /*isAssertion*/
)
p.finishMutatedNode(bin.AsNode())
}
}
}
case ast.KindJSDocTemplateTag:
if fun := getFunctionLikeHost(parent); fun != nil {
if fun.TypeParameters() == nil && fun.FunctionLikeData().FullSignature == nil {
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(fun)
}
} else if parent.Kind == ast.KindClassDeclaration {
class := parent.AsClassDeclaration()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(parent)
}
} else if parent.Kind == ast.KindClassExpression {
class := parent.AsClassExpression()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(parent)
}
}
case ast.KindJSDocParameterTag:
if fun := getFunctionLikeHost(parent); fun != nil && fun.FunctionLikeData().FullSignature == nil {
parameterTag := tag.AsJSDocParameterOrPropertyTag()
if param, ok := findMatchingParameter(fun, parameterTag, jsDoc); ok {
if param.Type == nil && parameterTag.TypeExpression != nil {
param.AsParameterDeclaration().Type = p.reparseJSDocTypeLiteral(parameterTag.TypeExpression.Type())
}
if param.QuestionToken == nil {
if question := p.makeQuestionIfOptional(parameterTag); question != nil {
param.QuestionToken = question
}
}
p.finishMutatedNode(param.AsNode())
}
}
case ast.KindJSDocThisTag:
if fun := getFunctionLikeHost(parent); fun != nil {
params := fun.Parameters()
if len(params) == 0 || (params[0].Name().Kind != ast.KindThisKeyword && !ast.IsThisIdentifier(params[0].Name())) {
thisParam := p.factory.NewParameterDeclaration(
nil, /* decorators */
nil, /* modifiers */
p.factory.NewIdentifier("this"),
nil, /* questionToken */
nil, /* type */
nil, /* initializer */
)
if tag.AsJSDocThisTag().TypeExpression != nil {
thisParam.AsParameterDeclaration().Type = p.addDeepCloneReparse(tag.AsJSDocThisTag().TypeExpression.Type())
}
p.finishReparsedNode(thisParam, tag.TagName())
newParams := p.nodeSliceArena.NewSlice(len(params) + 1)
newParams[0] = thisParam
for i, param := range params {
newParams[i+1] = param
}
fun.FunctionLikeData().Parameters = p.newNodeList(fun.ParameterList().Loc, newParams)
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocReturnTag:
if fun := getFunctionLikeHost(parent); fun != nil && fun.FunctionLikeData().FullSignature == nil {
if fun.Type() == nil && tag.TypeExpression() != nil {
fun.FunctionLikeData().Type = p.addDeepCloneReparse(tag.TypeExpression().Type())
p.finishMutatedNode(fun)
}
}
case ast.KindJSDocReadonlyTag, ast.KindJSDocPrivateTag, ast.KindJSDocPublicTag, ast.KindJSDocProtectedTag, ast.KindJSDocOverrideTag:
if parent.Kind == ast.KindExpressionStatement {
parent = parent.Expression()
}
switch parent.Kind {
case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
// In object literals these aren't class-like members, so JSDoc modifiers like @override
// or @readonly aren't real modifiers there; reparsing them produces spurious grammar errors (#4437).
if p.parsingContexts&(1<<PCObjectLiteralMembers) != 0 {
return
}
fallthrough
case ast.KindPropertyDeclaration, ast.KindConstructor, ast.KindBinaryExpression:
var keyword ast.Kind
switch tag.Kind {
case ast.KindJSDocReadonlyTag:
keyword = ast.KindReadonlyKeyword
case ast.KindJSDocPrivateTag:
keyword = ast.KindPrivateKeyword
case ast.KindJSDocPublicTag:
keyword = ast.KindPublicKeyword
case ast.KindJSDocProtectedTag:
keyword = ast.KindProtectedKeyword
case ast.KindJSDocOverrideTag:
keyword = ast.KindOverrideKeyword
}
modifier := p.factory.NewModifier(keyword)
modifier.Loc = tag.Loc
modifier.Flags = p.contextFlags | ast.NodeFlagsReparsed
var nodes []*ast.Node
var loc core.TextRange
if parent.Modifiers() == nil {
nodes = p.nodeSliceArena.NewSlice(1)
nodes[0] = modifier
loc = tag.Loc
} else {
nodes = append(parent.ModifierNodes(), modifier)
loc = parent.Modifiers().Loc
}
parent.AsMutable().SetModifiers(p.newModifierList(loc, nodes))
p.finishMutatedNode(parent)
}
case ast.KindJSDocImplementsTag:
if class := getClassLikeData(parent); class != nil {
implementsTag := tag.AsJSDocImplementsTag()
if class.HeritageClauses != nil {
if implementsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
return node.AsHeritageClause().Token == ast.KindImplementsKeyword
}); implementsClause != nil {
implementsClause.AsHeritageClause().Types.Nodes = append(implementsClause.AsHeritageClause().Types.Nodes, p.addDeepCloneReparse(implementsTag.ClassName))
p.finishMutatedNode(implementsClause)
return
}
}
typesList := p.newNodeList(implementsTag.ClassName.Loc, p.nodeSliceArena.NewSlice1(p.addDeepCloneReparse(implementsTag.ClassName)))
heritageClause := p.factory.NewHeritageClause(ast.KindImplementsKeyword, typesList)
p.finishReparsedNode(heritageClause, implementsTag.ClassName)
if class.HeritageClauses == nil {
heritageClauses := p.newNodeList(implementsTag.ClassName.Loc, p.nodeSliceArena.NewSlice1(heritageClause))
class.HeritageClauses = heritageClauses
} else {
class.HeritageClauses.Nodes = append(class.HeritageClauses.Nodes, heritageClause)
}
p.finishMutatedNode(parent)
}
case ast.KindJSDocAugmentsTag:
if class := getClassLikeData(parent); class != nil && class.HeritageClauses != nil {
if extendsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
return node.AsHeritageClause().Token == ast.KindExtendsKeyword
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
source := tag.ClassName().AsExpressionWithTypeArguments()
if ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
if target.TypeArguments == nil && source.TypeArguments != nil {
newArguments := p.nodeSliceArena.NewSlice(len(source.TypeArguments.Nodes))
for i, arg := range source.TypeArguments.Nodes {
newArguments[i] = p.addDeepCloneReparse(arg)
}
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
p.finishMutatedNode(target.AsNode())
}
}
}
}
}
}
func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterOrPropertyTag) *ast.Node {
var questionToken *ast.Node
if parameter.IsBracketed || parameter.TypeExpression != nil && parameter.TypeExpression.Type().Kind == ast.KindJSDocOptionalType {
questionToken = p.factory.NewToken(ast.KindQuestionToken)
questionToken.Loc = parameter.Loc
questionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
return questionToken
}
func findMatchingParameter(fun *ast.Node, parameterTag *ast.JSDocParameterOrPropertyTag, jsDoc *ast.Node) (*ast.ParameterDeclaration, bool) {
tagIndex := -1
paramCount := -1
for _, tag := range jsDoc.AsJSDoc().Tags.Nodes {
if tag.Kind == ast.KindJSDocParameterTag {
paramCount++
if tag.AsJSDocParameterOrPropertyTag() == parameterTag {
tagIndex = paramCount
break
}
}
}
for parameterIndex, parameter := range fun.Parameters() {
if parameter.Name().Kind == ast.KindIdentifier {
if parameterTag.Name().Kind == ast.KindIdentifier &&
((parameter.Name().Text() == parameterTag.Name().Text()) || (parameterIndex == tagIndex && len(parameterTag.Name().Text()) == 0)) {
return parameter.AsParameterDeclaration(), true
}
} else if parameterIndex == tagIndex {
return parameter.AsParameterDeclaration(), true
}
}
return nil, false
}
func skipSatisfiesExpressions(node *ast.Node) *ast.Node {
for node != nil && node.Kind == ast.KindSatisfiesExpression {
node = node.Expression()
}
return node
}
func getFunctionLikeHost(host *ast.Node) *ast.Node {
fun := host
switch host.Kind {
case ast.KindVariableStatement:
if nodes := host.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes; len(nodes) != 0 {
fun = nodes[0].Initializer()
}
case ast.KindPropertyAssignment, ast.KindPropertyDeclaration:
fun = host.Initializer()
case ast.KindExportAssignment, ast.KindReturnStatement:
fun = host.Expression()
case ast.KindExpressionStatement:
fun = ast.GetRightMostAssignedExpression(host.Expression())
}
fun = skipSatisfiesExpressions(fun)
if ast.IsFunctionLike(fun) {
return fun
}
return nil
}
func (p *Parser) makeNewCast(t *ast.TypeNode, e *ast.Node, isAssertion bool) *ast.Node {
var assert *ast.Node
if isAssertion {
assert = p.factory.NewAsExpression(e, t)
} else {
assert = p.factory.NewSatisfiesExpression(e, t)
}
p.finishNodeWithEnd(assert, e.Pos(), e.End())
return assert
}
func getClassLikeData(parent *ast.Node) *ast.ClassLikeBase {
var class *ast.ClassLikeBase
switch parent.Kind {
case ast.KindClassDeclaration:
class = parent.AsClassDeclaration().ClassLikeData()
case ast.KindClassExpression:
class = parent.AsClassExpression().ClassLikeData()
}
return class
}
func (p *Parser) createExportModifier(locationNode *ast.Node) *ast.ModifierList {
exportModifier := p.factory.NewModifier(ast.KindExportKeyword)
exportModifier.Loc = locationNode.Loc
exportModifier.Flags = p.contextFlags | ast.NodeFlagsReparsed
nodes := p.nodeSliceArena.NewSlice1(exportModifier)
return p.newModifierList(locationNode.Loc, nodes)
}
// getInnermostNameOfJSDocNamespace returns the innermost identifier from a
// JSDoc namespace chain (ModuleDeclaration). For a simple identifier, it returns
// the identifier itself. For "A.B.C", it returns the identifier "C".
func (p *Parser) getInnermostNameOfJSDocNamespace(fullName *ast.Node) *ast.Node {
if fullName == nil {
return nil
}
for fullName.Kind == ast.KindModuleDeclaration {
body := fullName.AsModuleDeclaration().Body
if body == nil {
return fullName.Name()
}
fullName = body
}
return fullName
}
// wrapInJSDocNamespace wraps a statement (typically a type alias) in namespace
// declarations corresponding to a JSDoc dotted name. For example, given name
// "A.B.C" and a type alias for C, this produces:
//
// namespace A { namespace B { type C = ... } }
//
// If the name is a simple identifier (not a ModuleDeclaration), it returns the
// statement as-is.
func (p *Parser) wrapInJSDocNamespace(fullName *ast.Node, statement *ast.Node, nested bool) *ast.Node {
if fullName == nil || !ast.IsModuleDeclaration(fullName) {
return statement
}
// Recursively wrap from outermost to innermost. Inner namespaces always get an export modifier
// so members are accessible via dotted access from outside. The outermost namespace is treated as
// exported only in module files via IsImplicitlyExportedJSDocDeclaration (in the binder), so it
// does not get an explicit export modifier here.
wrapped := p.wrapInJSDocNamespace(fullName.Body(), statement, true /*nested*/)
block := p.factory.NewModuleBlock(p.newNodeList(fullName.Loc, p.nodeSliceArena.NewSlice1(wrapped)))
p.finishReparsedNode(block, fullName)
var modifiers *ast.ModifierList
if nested {
modifiers = p.createExportModifier(fullName)
}
result := p.factory.NewModuleDeclaration(modifiers, ast.KindNamespaceKeyword, p.addDeepCloneReparse(fullName.Name()), block)
p.finishReparsedNode(result, fullName)
p.reparsedClones = append(p.reparsedClones, result)
return result
}

View File

@@ -0,0 +1,14 @@
package parser
// ParseFlags
type ParseFlags uint32
const (
ParseFlagsNone ParseFlags = 0
ParseFlagsYield ParseFlags = 1 << 0
ParseFlagsAwait ParseFlags = 1 << 1
ParseFlagsType ParseFlags = 1 << 2
ParseFlagsIgnoreMissingOpenBrace ParseFlags = 1 << 4
ParseFlagsJSDoc ParseFlags = 1 << 5
)

View File

@@ -0,0 +1,56 @@
package parser
import (
"slices"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
)
func getLanguageVariant(scriptKind core.ScriptKind) core.LanguageVariant {
switch scriptKind {
case core.ScriptKindTSX, core.ScriptKindJSX, core.ScriptKindJS, core.ScriptKindJSON:
// .tsx and .jsx files are treated as jsx language variant.
return core.LanguageVariantJSX
}
return core.LanguageVariantStandard
}
func tokenIsIdentifierOrKeyword(token ast.Kind) bool {
return token >= ast.KindIdentifier
}
func tokenIsIdentifierOrKeywordOrGreaterThan(token ast.Kind) bool {
return token == ast.KindGreaterThanToken || tokenIsIdentifierOrKeyword(token)
}
func GetJSDocCommentRanges(f *ast.NodeFactory, commentRanges []ast.CommentRange, node *ast.Node, text string) []ast.CommentRange {
switch node.Kind {
case ast.KindParameter, ast.KindTypeParameter, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindParenthesizedExpression, ast.KindVariableDeclaration, ast.KindExportSpecifier:
for commentRange := range scanner.GetTrailingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
for commentRange := range scanner.GetLeadingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
default:
for commentRange := range scanner.GetLeadingCommentRanges(f, text, node.Pos()) {
commentRanges = append(commentRanges, commentRange)
}
}
// Keep if the comment starts with '/**' but not if it is '/**/'
return slices.DeleteFunc(commentRanges, func(comment ast.CommentRange) bool {
commentStart := comment.Pos()
commentLen := comment.End() - commentStart
return comment.End() > node.End() || commentLen < 4 || text[commentStart+1] != '*' || text[commentStart+2] != '*' || text[commentStart+3] == '/'
})
}
func isKeywordOrPunctuation(token ast.Kind) bool {
return ast.IsKeywordKind(token) || ast.IsPunctuationKind(token)
}
func isJSDocLikeText(text string) bool {
return len(text) >= 4 && text[1] == '*' && text[2] == '*' && text[3] != '/'
}