diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 4b558549ba..3d344c6f9e 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -8920,6 +8920,10 @@ func (node *JSDoc) Clone(f NodeFactoryCoercible) *Node { return cloneNode(f.AsNodeFactory().NewJSDoc(node.Comment, node.Tags), node.AsNode(), f.AsNodeFactory().hooks) } +func (node *Node) IsJSDoc() bool { + return node.Kind == KindJSDoc +} + type JSDocTagBase struct { NodeBase TagName *IdentifierNode diff --git a/internal/core/core.go b/internal/core/core.go index 10147c8b3f..5a8715fbab 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -570,3 +570,18 @@ func SingleElementSlice[T any](element *T) []*T { } return []*T{element} } + +func ConcatenateSeq[T any](seqs ...iter.Seq[T]) iter.Seq[T] { + return func(yield func(T) bool) { + for _, seq := range seqs { + if seq == nil { + continue + } + for e := range seq { + if !yield(e) { + return + } + } + } + } +} diff --git a/internal/fourslash/_scripts/failingTests.txt b/internal/fourslash/_scripts/failingTests.txt index 5d0dbc1e5c..11cc759852 100644 --- a/internal/fourslash/_scripts/failingTests.txt +++ b/internal/fourslash/_scripts/failingTests.txt @@ -67,13 +67,12 @@ TestCompletionInJsDocQualifiedNames TestCompletionInNamedImportLocation TestCompletionInUncheckedJSFile TestCompletionInfoWithExplicitTypeArguments +TestCompletionJSDocNamePath TestCompletionListAfterRegularExpressionLiteral01 TestCompletionListAfterRegularExpressionLiteral1 TestCompletionListAfterStringLiteral1 TestCompletionListAndMemberListOnCommentedDot -TestCompletionListAndMemberListOnCommentedLine TestCompletionListAndMemberListOnCommentedWhiteSpace -TestCompletionListAtInvalidLocations TestCompletionListBuilderLocations_VariableDeclarations TestCompletionListCladule TestCompletionListClassMembers @@ -89,8 +88,6 @@ TestCompletionListInClassExpressionWithTypeParameter TestCompletionListInClassStaticBlocks TestCompletionListInClosedFunction05 TestCompletionListInComments -TestCompletionListInComments2 -TestCompletionListInComments3 TestCompletionListInExtendsClause TestCompletionListInImportClause01 TestCompletionListInImportClause05 @@ -151,6 +148,7 @@ TestCompletionsJSDocImportTagAttributesErrorModuleSpecifier1 TestCompletionsJSDocImportTagEmptyModuleSpecifier1 TestCompletionsJSDocNoCrash1 TestCompletionsJSDocNoCrash2 +TestCompletionsJSDocNoCrash3 TestCompletionsJsPropertyAssignment TestCompletionsJsdocParamTypeBeforeName TestCompletionsJsdocTag @@ -265,9 +263,11 @@ TestJavascriptModules20 TestJavascriptModules21 TestJavascriptModulesTypeImport TestJsDocFunctionSignatures3 +TestJsDocFunctionTypeCompletionsNoCrash TestJsDocGenerics1 TestJsdocExtendsTagCompletion TestJsdocImplementsTagCompletion +TestJsdocImportTagCompletion1 TestJsdocLink_findAllReferences1 TestJsdocOverloadTagCompletion TestJsdocParamTagSpecialKeywords @@ -275,6 +275,7 @@ TestJsdocParameterNameCompletion TestJsdocPropTagCompletion TestJsdocPropertyTagCompletion TestJsdocSatisfiesTagCompletion1 +TestJsdocSatisfiesTagCompletion2 TestJsdocTemplatePrototypeCompletions TestJsdocTemplateTagCompletion TestJsdocThrowsTagCompletion @@ -297,7 +298,6 @@ TestMemberListOnConstructorType TestMemberListOnExplicitThis TestMemberListOnThisInClassWithPrivates TestModuleMembersOfGenericType -TestNoCompletionListOnCommentsInsideObjectLiterals TestNodeModulesImportCompletions1 TestPathCompletionsAllowModuleAugmentationExtensions TestPathCompletionsAllowTsExtensions diff --git a/internal/fourslash/tests/gen/completionJSDocNamePath_test.go b/internal/fourslash/tests/gen/completionJSDocNamePath_test.go index 85b05098f7..686e87e682 100644 --- a/internal/fourslash/tests/gen/completionJSDocNamePath_test.go +++ b/internal/fourslash/tests/gen/completionJSDocNamePath_test.go @@ -9,7 +9,7 @@ import ( func TestCompletionJSDocNamePath(t *testing.T) { t.Parallel() - + t.Skip() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @noLib: true /** diff --git a/internal/fourslash/tests/gen/completionListAndMemberListOnCommentedLine_test.go b/internal/fourslash/tests/gen/completionListAndMemberListOnCommentedLine_test.go index ea4da7a03d..4bac4f53d8 100644 --- a/internal/fourslash/tests/gen/completionListAndMemberListOnCommentedLine_test.go +++ b/internal/fourslash/tests/gen/completionListAndMemberListOnCommentedLine_test.go @@ -9,7 +9,7 @@ import ( func TestCompletionListAndMemberListOnCommentedLine(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// /**/ var` diff --git a/internal/fourslash/tests/gen/completionListAtInvalidLocations_test.go b/internal/fourslash/tests/gen/completionListAtInvalidLocations_test.go index be54e4ccd4..a98f4825b4 100644 --- a/internal/fourslash/tests/gen/completionListAtInvalidLocations_test.go +++ b/internal/fourslash/tests/gen/completionListAtInvalidLocations_test.go @@ -9,7 +9,7 @@ import ( func TestCompletionListAtInvalidLocations(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = ` var v1 = ''; " /*openString1*/ diff --git a/internal/fourslash/tests/gen/completionListInComments2_test.go b/internal/fourslash/tests/gen/completionListInComments2_test.go index a8330b3d1e..1c55c1f4d0 100644 --- a/internal/fourslash/tests/gen/completionListInComments2_test.go +++ b/internal/fourslash/tests/gen/completionListInComments2_test.go @@ -9,7 +9,7 @@ import ( func TestCompletionListInComments2(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// */{| "name" : "1" |}` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) diff --git a/internal/fourslash/tests/gen/completionListInComments3_test.go b/internal/fourslash/tests/gen/completionListInComments3_test.go index ca3dcb58ea..d677ced4bc 100644 --- a/internal/fourslash/tests/gen/completionListInComments3_test.go +++ b/internal/fourslash/tests/gen/completionListInComments3_test.go @@ -9,7 +9,7 @@ import ( func TestCompletionListInComments3(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = ` /*{| "name": "1" |} /* {| "name": "2" |} diff --git a/internal/fourslash/tests/gen/completionsJSDocNoCrash3_test.go b/internal/fourslash/tests/gen/completionsJSDocNoCrash3_test.go index fc7ec3a086..bb0991f29e 100644 --- a/internal/fourslash/tests/gen/completionsJSDocNoCrash3_test.go +++ b/internal/fourslash/tests/gen/completionsJSDocNoCrash3_test.go @@ -11,7 +11,7 @@ import ( func TestCompletionsJSDocNoCrash3(t *testing.T) { t.Parallel() - + t.Skip() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @strict: true // @filename: index.ts diff --git a/internal/fourslash/tests/gen/jsDocFunctionTypeCompletionsNoCrash_test.go b/internal/fourslash/tests/gen/jsDocFunctionTypeCompletionsNoCrash_test.go index 87f3d00ace..4c8e8c66c7 100644 --- a/internal/fourslash/tests/gen/jsDocFunctionTypeCompletionsNoCrash_test.go +++ b/internal/fourslash/tests/gen/jsDocFunctionTypeCompletionsNoCrash_test.go @@ -9,7 +9,7 @@ import ( func TestJsDocFunctionTypeCompletionsNoCrash(t *testing.T) { t.Parallel() - + t.Skip() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `/** * @returns {function/**/(): string} diff --git a/internal/fourslash/tests/gen/jsdocImportTagCompletion1_test.go b/internal/fourslash/tests/gen/jsdocImportTagCompletion1_test.go index 58e54240ac..c1333f3a9b 100644 --- a/internal/fourslash/tests/gen/jsdocImportTagCompletion1_test.go +++ b/internal/fourslash/tests/gen/jsdocImportTagCompletion1_test.go @@ -9,7 +9,7 @@ import ( func TestJsdocImportTagCompletion1(t *testing.T) { t.Parallel() - + t.Skip() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @allowJS: true // @checkJs: true diff --git a/internal/fourslash/tests/gen/jsdocSatisfiesTagCompletion2_test.go b/internal/fourslash/tests/gen/jsdocSatisfiesTagCompletion2_test.go index 6ee6a5f120..fcd6023dd3 100644 --- a/internal/fourslash/tests/gen/jsdocSatisfiesTagCompletion2_test.go +++ b/internal/fourslash/tests/gen/jsdocSatisfiesTagCompletion2_test.go @@ -9,7 +9,7 @@ import ( func TestJsdocSatisfiesTagCompletion2(t *testing.T) { t.Parallel() - + t.Skip() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @noEmit: true // @allowJS: true diff --git a/internal/fourslash/tests/gen/noCompletionListOnCommentsInsideObjectLiterals_test.go b/internal/fourslash/tests/gen/noCompletionListOnCommentsInsideObjectLiterals_test.go index ca36a1adae..3abcebf60d 100644 --- a/internal/fourslash/tests/gen/noCompletionListOnCommentsInsideObjectLiterals_test.go +++ b/internal/fourslash/tests/gen/noCompletionListOnCommentsInsideObjectLiterals_test.go @@ -9,7 +9,7 @@ import ( func TestNoCompletionListOnCommentsInsideObjectLiterals(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `module ObjectLiterals { interface MyPoint { diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 670b10c444..a0fef96a07 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -399,6 +399,7 @@ func getCompletionData(program *compiler.Program, typeChecker *checker.Checker, isInSnippetScope := false if insideComment != nil { // !!! jsdoc + return nil } // The decision to provide completion depends on the contextToken, which is determined through the previousToken. diff --git a/internal/ls/format.go b/internal/ls/format.go index 199c983d38..1a5677e535 100644 --- a/internal/ls/format.go +++ b/internal/ls/format.go @@ -2,11 +2,14 @@ package ls import ( "context" + "iter" "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/format" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/scanner" ) func toFormatCodeSettings(opt *lsproto.FormattingOptions) *format.FormatCodeSettings { @@ -123,3 +126,53 @@ func (l *LanguageService) getFormattingEditsAfterKeystroke( } return nil } + +// Unlike the TS implementation, this function *will not* compute default values for +// `precedingToken` and `tokenAtPosition`. +// It is the caller's responsibility to call `astnav.GetTokenAtPosition` to compute a default `tokenAtPosition`, +// or `astnav.FindPrecedingToken` to compute a default `precedingToken`. +func getRangeOfEnclosingComment( + file *ast.SourceFile, + position int, + precedingToken *ast.Node, + tokenAtPosition *ast.Node, +) *ast.CommentRange { + jsdoc := ast.FindAncestor(tokenAtPosition, (*ast.Node).IsJSDoc) + if jsdoc != nil { + tokenAtPosition = jsdoc.Parent + } + tokenStart := astnav.GetStartOfNode(tokenAtPosition, file, false /*includeJSDoc*/) + if tokenStart <= position && position < tokenAtPosition.End() { + return nil + } + + // Between two consecutive tokens, all comments are either trailing on the former + // or leading on the latter (and none are in both lists). + var trailingRangesOfPreviousToken iter.Seq[ast.CommentRange] + if precedingToken != nil { + trailingRangesOfPreviousToken = scanner.GetTrailingCommentRanges(&ast.NodeFactory{}, file.Text(), position) + } + leadingRangesOfNextToken := getLeadingCommentRangesOfNode(tokenAtPosition, file) + commentRanges := core.ConcatenateSeq(trailingRangesOfPreviousToken, leadingRangesOfNextToken) + for commentRange := range commentRanges { + // The end marker of a single-line comment does not include the newline character. + // In the following case where the cursor is at `^`, we are inside a comment: + // + // // asdf ^\n + // + // But for closed multi-line comments, we don't want to be inside the comment in the following case: + // + // /* asdf */^ + // + // Internally, we represent the end of the comment prior to the newline and at the '/', respectively. + // + // However, unterminated multi-line comments lack a `/`, end at the end of the file, and *do* contain their end. + // + if commentRange.ContainsExclusive(position) || + position == commentRange.End() && + (commentRange.Kind == ast.KindSingleLineCommentTrivia || position == len(file.Text())) { + return &commentRange + } + } + return nil +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 9f82ce42f9..7b047eac9c 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -3,6 +3,7 @@ package ls import ( "cmp" "fmt" + "iter" "slices" "strings" @@ -133,9 +134,8 @@ func isExportSpecifierAlias(referenceLocation *ast.Identifier, exportSpecifier * } } -// !!! formatting function func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange { - return nil + return getRangeOfEnclosingComment(file, position, nil /*precedingToken*/, tokenAtPosition) } func hasChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) bool { @@ -1628,3 +1628,10 @@ func findContainingList(node *ast.Node, file *ast.SourceFile) *ast.NodeList { astnav.VisitEachChildAndJSDoc(node.Parent, file, nodeVisitor) return list } + +func getLeadingCommentRangesOfNode(node *ast.Node, file *ast.SourceFile) iter.Seq[ast.CommentRange] { + if node.Kind == ast.KindJsxText { + return nil + } + return scanner.GetLeadingCommentRanges(&ast.NodeFactory{}, file.Text(), node.Pos()) +}