Skip to content

Use correct ResolutionMode for files when resolving/loading files #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ast

import (
"fmt"
"strings"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -5760,6 +5761,53 @@ func IsImportAttributes(node *Node) bool {
return node.Kind == KindImportAttributes
}

func (node *ImportAttributesNode) GetResolutionModeOverride( /* !!! grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void*/ ) (core.ResolutionMode, bool) {
if node == nil {
return core.ResolutionModeNone, false
}

attributes := node.AsImportAttributes().Attributes

if len(attributes.Nodes) != 1 {
// !!!
// grammarErrorOnNode?.(
// node,
// node.token === SyntaxKind.WithKeyword
// ? Diagnostics.Type_import_attributes_should_have_exactly_one_key_resolution_mode_with_value_import_or_require
// : Diagnostics.Type_import_assertions_should_have_exactly_one_key_resolution_mode_with_value_import_or_require,
// );
return core.ResolutionModeNone, false
}

elem := attributes.Nodes[0].AsImportAttribute()
if !IsStringLiteralLike(elem.Name()) {
return core.ResolutionModeNone, false
}
if elem.Name().Text() != "resolution-mode" {
// !!!
// grammarErrorOnNode?.(
// elem.name,
// node.token === SyntaxKind.WithKeyword
// ? Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_attributes
// : Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_assertions,
// );
return core.ResolutionModeNone, false
}
if !IsStringLiteralLike(elem.Value) {
return core.ResolutionModeNone, false
}
if elem.Value.Text() != "import" && elem.Value.Text() != "require" {
// !!!
// grammarErrorOnNode?.(elem.value, Diagnostics.resolution_mode_should_be_either_require_or_import);
return core.ResolutionModeNone, false
}
if elem.Value.Text() == "import" {
return core.ResolutionModeESM, true
} else {
return core.ModuleKindCommonJS, true
}
}

// TypeQueryNode

type TypeQueryNode struct {
Expand Down Expand Up @@ -7218,6 +7266,10 @@ func (node *JSDocImportTag) ForEachChild(v Visitor) bool {
return visit(v, node.TagName) || visit(v, node.ImportClause) || visit(v, node.ModuleSpecifier) || visit(v, node.Attributes) || visitNodeList(v, node.Comment)
}

func (node *Node) IsJSDocImportTag() bool {
return node.Kind == KindJSDocImportTag
}

// JSDocCallbackTag
type JSDocCallbackTag struct {
JSDocTagBase
Expand Down Expand Up @@ -7491,6 +7543,71 @@ func (node *SourceFile) LineMap() []core.TextPos {
return lineMap
}

func (file *SourceFile) CollectDynamicImportOrRequireOrJsDocImportCalls() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method modifies the source file; I suspect we'll need some additional protection later if we start sharing these ASTs between programs so that two programs can't possibly race to be the first to update this info (note lineMap above). But I assume it doesn't matter at the moment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though, I do wonder if we can come up with some method by which we don't do this here, but instead as a part of parsing or binding themselves...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like this could be done at parse time, but we are specifically just setting the NodeFlagsPossiblyContainsDynamicImport flag in parseImportType(), so I guess I'd presume there's some important reason to defer it. Maybe to do with incremental parsing (which might not be valid anymore)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is already being handled in Parser.

lastIndex := 0
for {
index := strings.Index(file.Text[lastIndex:], "import")
if index == -1 {
break
}
index += lastIndex
node := file.GetNodeAtPosition(index, false /* !!! isJavaScriptFile */)
// if isJavaScriptFile && isRequireCall(node /*requireStringLiteralLikeArgument*/, true) {
// setParentRecursive(node /*incremental*/, false) // we need parent data on imports before the program is fully bound, so we ensure it's set here
// imports = append(imports, node.arguments[0])
// } else
if IsImportCall(node) && len(node.Arguments()) >= 1 && IsStringLiteralLike(node.Arguments()[0]) {
// we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error.
SetParentInChildren(node) // we need parent data on imports before the program is fully bound, so we ensure it's set here
file.Imports = append(file.Imports, node.Arguments()[0])
} else if IsLiteralImportTypeNode(node) {
SetParentInChildren(node) // we need parent data on imports before the program is fully bound, so we ensure it's set here
file.Imports = append(file.Imports, node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal)
}
// else if isJavaScriptFile && isJSDocImportTag(node) {
// const moduleNameExpr = getExternalModuleName(node)
// if moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text {
// setParentRecursive(node /*incremental*/, false)
// imports = append(imports, moduleNameExpr)
// }
// }
lastIndex = min(index+len("import"), len(file.Text))
}
}

// Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files
func (file *SourceFile) GetNodeAtPosition(position int, isJavaScriptFile bool) *Node {
current := file.AsNode()
for {
var child *Node
if isJavaScriptFile /* && hasJSDocNodes(current) */ {
for _, jsDoc := range current.JSDoc(file) {
if nodeContainsPosition(jsDoc, position) {
child = jsDoc
break
}
}
}
if child == nil {
current.ForEachChild(func(node *Node) bool {
if nodeContainsPosition(node, position) {
child = node
return true
}
return false
})
}
if child == nil {
return current
}
current = child
}
}

func nodeContainsPosition(node *Node, position int) bool {
return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile)
}

func IsSourceFile(node *Node) bool {
return node.Kind == KindSourceFile
}
Expand Down
26 changes: 26 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,18 @@ func IsInJSFile(node *Node) bool {
return node != nil && node.Flags&NodeFlagsJavaScriptFile != 0
}

func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
if IsCallExpression(node) {
callExpression := node.AsCallExpression()
if len(callExpression.Arguments.Nodes) == 1 {
if IsIdentifier(callExpression.Expression) && callExpression.Expression.AsIdentifier().Text == "require" {
return !requireStringLiteralLikeArgument || IsStringLiteralLike(callExpression.Arguments.Nodes[0])
}
}
}
return false
}

func IsDeclaration(node *Node) bool {
if node.Kind == KindTypeParameter {
return node.Parent != nil
Expand Down Expand Up @@ -1196,6 +1208,20 @@ func IsImportOrExportSpecifier(node *Node) bool {
return IsImportSpecifier(node) || IsExportSpecifier(node)
}

func IsExclusivelyTypeOnlyImportOrExport(decl *Node) bool {
Copy link
Preview

Copilot AI Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function IsExclusivelyTypeOnlyImportOrExport is missing a case for KindImportEqualsDeclaration. Please add this case to ensure all relevant node kinds are covered.

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

switch decl.Kind {
case KindImportDeclaration:
importDecl := decl.AsImportDeclaration()
if importDecl.ImportClause != nil {
return importDecl.ImportClause.AsImportClause().IsTypeOnly
}
case KindExportDeclaration:
return decl.AsExportDeclaration().IsTypeOnly
}

return false
}

func isVoidZero(node *Node) bool {
return IsVoidExpression(node) && IsNumericLiteral(node.Expression()) && node.Expression().Text() == "0"
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4847,7 +4847,7 @@ func (c *Checker) getQuickTypeOfExpression(node *ast.Node) *Type {
return nil
// Optimize for the common case of a call to a function with a single non-generic call
// signature where we can just fetch the return type without checking the arguments.
case ast.IsCallExpression(expr) && expr.Expression().Kind != ast.KindSuperKeyword && !isRequireCall(expr, true /*requireStringLiteralLikeArgument*/) && !c.isSymbolOrSymbolForCall(expr):
case ast.IsCallExpression(expr) && expr.Expression().Kind != ast.KindSuperKeyword && !ast.IsRequireCall(expr, true /*requireStringLiteralLikeArgument*/) && !c.isSymbolOrSymbolForCall(expr):
if isCallChain(expr) {
return c.getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr)
}
Expand Down
14 changes: 1 addition & 13 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func isVariableDeclarationInitializedWithRequireHelper(node *ast.Node, allowAcce
if allowAccessedRequire {
initializer = getLeftmostAccessExpression(initializer)
}
return isRequireCall(initializer, true /*requireStringLiteralLikeArgument*/)
return ast.IsRequireCall(initializer, true /*requireStringLiteralLikeArgument*/)
}
return false
}
Expand All @@ -167,18 +167,6 @@ func getLeftmostAccessExpression(expr *ast.Node) *ast.Node {
return expr
}

func isRequireCall(node *ast.Node, requireStringLiteralLikeArgument bool) bool {
if ast.IsCallExpression(node) {
callExpression := node.AsCallExpression()
if len(callExpression.Arguments.Nodes) == 1 {
if ast.IsIdentifier(callExpression.Expression) && callExpression.Expression.AsIdentifier().Text == "require" {
return !requireStringLiteralLikeArgument || ast.IsStringLiteralLike(callExpression.Arguments.Nodes[0])
}
}
}
return false
}

func isStaticPrivateIdentifierProperty(s *ast.Symbol) bool {
return s.ValueDeclaration != nil && ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration) && ast.IsStatic(s.ValueDeclaration)
}
Expand Down
Loading
Loading