Skip to content
Merged
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
29 changes: 29 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,26 @@ func (n *Node) ClassName() *Node {
panic("Unhandled case in Node.ClassName: " + n.Kind.String())
}

func (n *Node) PostfixToken() *Node {
switch n.Kind {
case KindParameter:
return n.AsParameterDeclaration().QuestionToken
case KindMethodDeclaration:
return n.AsMethodDeclaration().PostfixToken
case KindShorthandPropertyAssignment:
return n.AsShorthandPropertyAssignment().PostfixToken
case KindMethodSignature:
return n.AsMethodSignatureDeclaration().PostfixToken
case KindPropertySignature:
return n.AsPropertySignatureDeclaration().PostfixToken
case KindPropertyAssignment:
return n.AsPropertyAssignment().PostfixToken
case KindPropertyDeclaration:
return n.AsPropertyDeclaration().PostfixToken
}
return nil
}

// Determines if `n` contains `descendant` by walking up the `Parent` pointers from `descendant`. This method panics if
// `descendant` or one of its ancestors is not parented except when that node is a `SourceFile`.
func (n *Node) Contains(descendant *Node) bool {
Expand Down Expand Up @@ -10382,6 +10402,7 @@ type SourceFile struct {

// Fields set by parser
diagnostics []*Diagnostic
jsDiagnostics []*Diagnostic
jsdocDiagnostics []*Diagnostic
LanguageVariant core.LanguageVariant
ScriptKind core.ScriptKind
Expand Down Expand Up @@ -10469,6 +10490,14 @@ func (node *SourceFile) SetDiagnostics(diags []*Diagnostic) {
node.diagnostics = diags
}

func (node *SourceFile) JSDiagnostics() []*Diagnostic {
return node.jsDiagnostics
}

func (node *SourceFile) SetJSDiagnostics(diags []*Diagnostic) {
node.jsDiagnostics = diags
}

func (node *SourceFile) JSDocDiagnostics() []*Diagnostic {
return node.jsdocDiagnostics
}
Expand Down
17 changes: 8 additions & 9 deletions internal/ast/modifierflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@ const (
// Syntactic-only modifiers
ModifierFlagsExport ModifierFlags = 1 << 5 // Declarations
ModifierFlagsAbstract ModifierFlags = 1 << 6 // Class/Method/ConstructSignature
ModifierFlagsAmbient ModifierFlags = 1 << 7 // Declarations
ModifierFlagsAmbient ModifierFlags = 1 << 7 // Declarations (declare keyword)
ModifierFlagsStatic ModifierFlags = 1 << 8 // Property/Method
ModifierFlagsAccessor ModifierFlags = 1 << 9 // Property
ModifierFlagsAsync ModifierFlags = 1 << 10 // Property/Method/Function
ModifierFlagsDefault ModifierFlags = 1 << 11 // Function/Class (export default declaration)
ModifierFlagsConst ModifierFlags = 1 << 12 // Const enum
ModifierFlagsIn ModifierFlags = 1 << 13 // Contravariance modifier
ModifierFlagsOut ModifierFlags = 1 << 14 // Covariance modifier
ModifierFlagsDecorator ModifierFlags = 1 << 15 // Contains a decorator.
ModifierFlagsImmediate ModifierFlags = 1 << 16 // Parameter
ModifierFlagsDecorator ModifierFlags = 1 << 15 // Contains a decorator
// JSDoc-only modifiers
ModifierFlagsDeprecated ModifierFlags = 1 << 17 // Deprecated tag.
ModifierFlagsJSDocImmediate ModifierFlags = 1 << 18 // Parameter
ModifierFlagsDeprecated ModifierFlags = 1 << 16 // Deprecated tag
// Cache-only JSDoc-modifiers. Should match order of Syntactic/JSDoc modifiers, above.
ModifierFlagsJSDocPublic ModifierFlags = 1 << 23 // if this value changes, `selectEffectiveModifierFlags` must change accordingly
ModifierFlagsJSDocPrivate ModifierFlags = 1 << 24
Expand All @@ -36,19 +34,20 @@ const (
ModifierFlagsHasComputedFlags ModifierFlags = 1 << 29 // Modifier flags have been computed

ModifierFlagsSyntacticOrJSDocModifiers = ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsReadonly | ModifierFlagsOverride
ModifierFlagsSyntacticOnlyModifiers = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsAbstract | ModifierFlagsStatic | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsDecorator | ModifierFlagsImmediate
ModifierFlagsSyntacticOnlyModifiers = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsAbstract | ModifierFlagsStatic | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsDecorator
ModifierFlagsSyntacticModifiers = ModifierFlagsSyntacticOrJSDocModifiers | ModifierFlagsSyntacticOnlyModifiers
ModifierFlagsJSDocCacheOnlyModifiers = ModifierFlagsJSDocPublic | ModifierFlagsJSDocPrivate | ModifierFlagsJSDocProtected | ModifierFlagsJSDocReadonly | ModifierFlagsJSDocOverride
ModifierFlagsJSDocOnlyModifiers = ModifierFlagsDeprecated | ModifierFlagsJSDocImmediate
ModifierFlagsJSDocOnlyModifiers = ModifierFlagsDeprecated
ModifierFlagsNonCacheOnlyModifiers = ModifierFlagsSyntacticOrJSDocModifiers | ModifierFlagsSyntacticOnlyModifiers | ModifierFlagsJSDocOnlyModifiers

ModifierFlagsAccessibilityModifier = ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected
// Accessibility modifiers and 'readonly' can be attached to a parameter in a constructor to make it a property.
ModifierFlagsParameterPropertyModifier = ModifierFlagsAccessibilityModifier | ModifierFlagsReadonly | ModifierFlagsOverride
ModifierFlagsNonPublicAccessibilityModifier = ModifierFlagsPrivate | ModifierFlagsProtected

ModifierFlagsTypeScriptModifier = ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsConst | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsImmediate
ModifierFlagsTypeScriptModifier = ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsConst | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut
ModifierFlagsExportDefault = ModifierFlagsExport | ModifierFlagsDefault
ModifierFlagsAll = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsStatic | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsDeprecated | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsImmediate | ModifierFlagsDecorator
ModifierFlagsAll = ModifierFlagsExport | ModifierFlagsAmbient | ModifierFlagsPublic | ModifierFlagsPrivate | ModifierFlagsProtected | ModifierFlagsStatic | ModifierFlagsReadonly | ModifierFlagsAbstract | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault | ModifierFlagsConst | ModifierFlagsDeprecated | ModifierFlagsOverride | ModifierFlagsIn | ModifierFlagsOut | ModifierFlagsDecorator
ModifierFlagsModifier = ModifierFlagsAll & ^ModifierFlagsDecorator
ModifierFlagsJavaScript = ModifierFlagsExport | ModifierFlagsStatic | ModifierFlagsAccessor | ModifierFlagsAsync | ModifierFlagsDefault
)
29 changes: 6 additions & 23 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1015,8 +1015,6 @@ func ModifierToFlag(token Kind) ModifierFlags {
return ModifierFlagsIn
case KindOutKeyword:
return ModifierFlagsOut
case KindImmediateKeyword:
return ModifierFlagsImmediate
case KindDecorator:
return ModifierFlagsDecorator
}
Expand Down Expand Up @@ -2086,10 +2084,7 @@ func IsComputedNonLiteralName(name *Node) bool {
}

func IsQuestionToken(node *Node) bool {
if node == nil {
return false
}
return node.Kind == KindQuestionToken
return node != nil && node.Kind == KindQuestionToken
}

func GetTextOfPropertyName(name *Node) string {
Expand Down Expand Up @@ -3552,23 +3547,7 @@ func ShouldTransformImportCall(fileName string, options *core.CompilerOptions, i
}

func HasQuestionToken(node *Node) bool {
switch node.Kind {
case KindParameter:
return node.AsParameterDeclaration().QuestionToken != nil
case KindMethodDeclaration:
return IsQuestionToken(node.AsMethodDeclaration().PostfixToken)
case KindShorthandPropertyAssignment:
return IsQuestionToken(node.AsShorthandPropertyAssignment().PostfixToken)
case KindMethodSignature:
return IsQuestionToken(node.AsMethodSignatureDeclaration().PostfixToken)
case KindPropertySignature:
return IsQuestionToken(node.AsPropertySignatureDeclaration().PostfixToken)
case KindPropertyAssignment:
return IsQuestionToken(node.AsPropertyAssignment().PostfixToken)
case KindPropertyDeclaration:
return IsQuestionToken(node.AsPropertyDeclaration().PostfixToken)
}
return false
return IsQuestionToken(node.PostfixToken())
}

func IsJsxOpeningLikeElement(node *Node) bool {
Expand Down Expand Up @@ -3877,3 +3856,7 @@ func IsJSDocNameReferenceContext(node *Node) bool {
return IsJSDocNameReference(node) || IsJSDocLinkLike(node)
}) != nil
}

func IsImportOrImportEqualsDeclaration(node *Node) bool {
return IsImportDeclaration(node) || IsImportEqualsDeclaration(node)
}
80 changes: 50 additions & 30 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2097,7 +2097,6 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil
c.renamedBindingElementsInTypes = nil
c.checkSourceElements(sourceFile.Statements.Nodes)
c.checkDeferredNodes(sourceFile)
c.checkJSDocNodes(sourceFile)
if ast.IsExternalOrCommonJSModule(sourceFile) {
c.checkExternalModuleExports(sourceFile.AsNode())
c.registerForUnusedIdentifiersCheck(sourceFile.AsNode())
Expand Down Expand Up @@ -2139,6 +2138,16 @@ func (c *Checker) checkSourceElement(node *ast.Node) bool {
}

func (c *Checker) checkSourceElementWorker(node *ast.Node) {
if node.Flags&ast.NodeFlagsHasJSDoc != 0 {
for _, jsdoc := range node.JSDoc(nil) {
c.checkJSDocComments(jsdoc)
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
c.checkJSDocComments(tag)
}
}
}
}
kind := node.Kind
if kind >= ast.KindFirstStatement && kind <= ast.KindLastStatement {
flowNode := node.FlowNodeData().FlowNode
Expand Down Expand Up @@ -2331,48 +2340,29 @@ func (c *Checker) checkDeferredNode(node *ast.Node) {
c.currentNode = saveCurrentNode
}

func (c *Checker) checkJSDocNodes(sourceFile *ast.SourceFile) {
// !!!
// This performs minimal checking of JSDoc nodes to ensure that @link references to entities are recorded
// for purposes of checking unused identifiers. We pass down a location node because the binder doesn't currently
// set parent references in JSDoc nodes.
for location, jsdocs := range sourceFile.JSDocCache() {
for _, jsdoc := range jsdocs {
if c.isCanceled() {
return
}
c.checkJSDocComments(jsdoc, location)
tags := jsdoc.AsJSDoc().Tags
if tags != nil {
for _, tag := range tags.Nodes {
c.checkJSDocComments(tag, location)
}
}
}
}
}

func (c *Checker) checkJSDocComments(node *ast.Node, location *ast.Node) {
func (c *Checker) checkJSDocComments(node *ast.Node) {
for _, comment := range node.Comments() {
c.checkJSDocComment(comment, location)
c.checkJSDocComment(comment)
}
}

func (c *Checker) checkJSDocComment(node *ast.Node, location *ast.Node) {
func (c *Checker) checkJSDocComment(node *ast.Node) {
// This performs minimal checking of JSDoc nodes to ensure that @link references to entities are recorded
// for purposes of checking unused identifiers.
switch node.Kind {
case ast.KindJSDocLink, ast.KindJSDocLinkCode, ast.KindJSDocLinkPlain:
c.resolveJSDocMemberName(node.Name(), location)
c.resolveJSDocMemberName(node.Name())
}
}

func (c *Checker) resolveJSDocMemberName(name *ast.Node, location *ast.Node) *ast.Symbol {
func (c *Checker) resolveJSDocMemberName(name *ast.Node) *ast.Symbol {
if name != nil && ast.IsEntityName(name) {
meaning := ast.SymbolFlagsType | ast.SymbolFlagsNamespace | ast.SymbolFlagsValue
if symbol := c.resolveEntityName(name, meaning, true /*ignoreErrors*/, true /*dontResolveAlias*/, location); symbol != nil {
if symbol := c.resolveEntityName(name, meaning, true /*ignoreErrors*/, true /*dontResolveAlias*/, nil); symbol != nil {
return symbol
}
if ast.IsQualifiedName(name) {
if symbol := c.resolveJSDocMemberName(name.AsQualifiedName().Left, location); symbol != nil {
if symbol := c.resolveJSDocMemberName(name.AsQualifiedName().Left); symbol != nil {
var t *Type
if symbol.Flags&ast.SymbolFlagsValue != 0 {
proto := c.getPropertyOfType(c.getTypeOfSymbol(symbol), "prototype")
Expand Down Expand Up @@ -6410,6 +6400,36 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
symbol = c.getMergedSymbol(core.OrElse(symbol.ExportSymbol, symbol))
// A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within
if ast.IsInJSFile(node) && target.Flags&ast.SymbolFlagsValue == 0 && !ast.IsTypeOnlyImportOrExportDeclaration(node) {
errorNode := core.OrElse(node.PropertyNameOrName(), node)
debug.Assert(node.Kind != ast.KindNamespaceExport)
if ast.IsExportSpecifier(node) {
diag := c.error(errorNode, diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files)
if sourceSymbol := ast.GetSourceFileOfNode(node).AsNode().Symbol(); sourceSymbol != nil {
if alreadyExportedSymbol := sourceSymbol.Exports[node.PropertyNameOrName().Text()]; alreadyExportedSymbol == target {
if exportingDeclaration := core.Find(alreadyExportedSymbol.Declarations, ast.IsJSTypeAliasDeclaration); exportingDeclaration != nil {
diag.AddRelatedInfo(NewDiagnosticForNode(exportingDeclaration, diagnostics.X_0_is_automatically_exported_here, alreadyExportedSymbol.Name))
}
}
}
} else {
debug.Assert(node.Kind != ast.KindVariableDeclaration)
specifierText := "..."
if importDeclaration := ast.FindAncestor(node, ast.IsImportOrImportEqualsDeclaration); importDeclaration != nil {
if moduleSpecifier := TryGetModuleSpecifierFromDeclaration(importDeclaration); moduleSpecifier != nil {
specifierText = moduleSpecifier.Text()
}
}
identifierText := symbol.Name
if ast.IsIdentifier(errorNode) {
identifierText = errorNode.Text()
}
importText := "import(\"" + specifierText + "\")." + identifierText
c.error(errorNode, diagnostics.X_0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation, identifierText, importText)
}
return
}
targetFlags := c.getSymbolFlags(target)
excludedMeanings := core.IfElse(symbol.Flags&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue) != 0, ast.SymbolFlagsValue, 0) |
core.IfElse(symbol.Flags&ast.SymbolFlagsType != 0, ast.SymbolFlagsType, 0) |
Expand Down Expand Up @@ -30460,7 +30480,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast
c.checkQualifiedName(name, CheckModeNormal)
}
if links.resolvedSymbol == nil && isJSDoc && ast.IsQualifiedName(name) {
return c.resolveJSDocMemberName(name, nil)
return c.resolveJSDocMemberName(name)
}
return links.resolvedSymbol
}
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ func (p *Program) getOptionsDiagnosticsOfConfigFile() []*ast.Diagnostic {
}

func (p *Program) getSyntacticDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return sourceFile.Diagnostics()
return core.Concatenate(sourceFile.Diagnostics(), sourceFile.JSDiagnostics())
}

func (p *Program) getBindDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
Expand Down
12 changes: 8 additions & 4 deletions internal/execute/tsc/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ func getFormatOptsOfSys(sys System) *diagnosticwriter.FormattingOptions {
type DiagnosticReporter = func(*ast.Diagnostic)

func QuietDiagnosticReporter(diagnostic *ast.Diagnostic) {}

func CreateDiagnosticReporter(sys System, w io.Writer, options *core.CompilerOptions) DiagnosticReporter {
if options.Quiet.IsTrue() {
return QuietDiagnosticReporter
}

formatOpts := getFormatOptsOfSys(sys)
writeDiagnostic := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticWithColorAndContext, diagnosticwriter.WriteFormatDiagnostic)
if shouldBePretty(sys, options) {
return func(diagnostic *ast.Diagnostic) {
diagnosticwriter.FormatDiagnosticWithColorAndContext(w, diagnostic, formatOpts)
fmt.Fprint(w, formatOpts.NewLine)
}
}
return func(diagnostic *ast.Diagnostic) {
writeDiagnostic(w, diagnostic, formatOpts)
fmt.Fprint(w, formatOpts.NewLine)
diagnosticwriter.WriteFormatDiagnostic(w, diagnostic, formatOpts)
}
}

Expand Down
19 changes: 8 additions & 11 deletions internal/ls/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@ import (
func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.DocumentUri) (lsproto.DocumentDiagnosticResponse, error) {
program, file := l.getProgramAndFile(uri)

diagnostics := make([][]*ast.Diagnostic, 0, 3)
if syntaxDiagnostics := program.GetSyntacticDiagnostics(ctx, file); len(syntaxDiagnostics) != 0 {
diagnostics = append(diagnostics, syntaxDiagnostics)
} else {
diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file))
// !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated?
// See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114
diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file))
if program.Options().GetEmitDeclarations() {
diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, file))
}
diagnostics := make([][]*ast.Diagnostic, 0, 4)
Copy link
Member

Choose a reason for hiding this comment

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

I had thought this code was correct originally; I guess you're changing it to match that VS Code on the client side requested syntax and semantic in sequence? (I've been trying to find equivalent code but I think this is just somewhere that differs in general with Strada given the fine-grained-ness of tsserver.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I simply noticed a behavioral difference, with semantic error red squiggles appearing and disappearing depending on whether there are syntax errors as you're typing. LSP doesn't distinguish between syntactic/semantic so we need to return all of them to get consistent behavior.

diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, file))
diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file))
// !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated?
// See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114
diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file))
if program.Options().GetEmitDeclarations() {
diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, file))
}

return lsproto.RelatedFullDocumentDiagnosticReportOrUnchangedDocumentDiagnosticReport{
Expand Down
Loading
Loading