Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7a4b3e2
merged server from main
johnfav03 Aug 28, 2025
0b437c2
implemented highlights and helper functions
johnfav03 Aug 28, 2025
57fa39d
Merge remote-tracking branch 'origin/main' into document-highlights
johnfav03 Sep 9, 2025
7342e59
fourslash testing for doc highlights
johnfav03 Sep 9, 2025
45906a2
fix compare LSP positions
gabritto Sep 10, 2025
a20e054
fix tests
gabritto Sep 10, 2025
c076d4b
update baselines
gabritto Sep 10, 2025
b08db3a
Merge remote-tracking branch 'origin/main' into document-highlights
johnfav03 Sep 10, 2025
c0fac1d
Merge branch 'gabritto/fixcompare' into document-highlights
johnfav03 Sep 10, 2025
a76671b
merged main
johnfav03 Sep 16, 2025
2fd1904
Merge remote-tracking branch 'origin/main' into document-highlights
johnfav03 Sep 17, 2025
6d814bb
documentHighlights with fourslash tests
johnfav03 Sep 17, 2025
1dade87
fixed minor change
johnfav03 Sep 17, 2025
e9d2bd6
removed unused baselines
johnfav03 Sep 17, 2025
45cd07b
removed repeated test
johnfav03 Sep 18, 2025
48e3163
fixed var declaration and nil checking nits
johnfav03 Sep 18, 2025
9c1245e
Merge branch 'main' into document-highlights
johnfav03 Sep 18, 2025
318a6f7
moved isWriteAccess and helpers from checker to ast
johnfav03 Sep 18, 2025
2d661ce
refactored renameArg and docHighlightArg into markerOrRangeArg
johnfav03 Sep 22, 2025
508be4d
fixed jsx case handling
johnfav03 Sep 22, 2025
a13337c
Merge remote-tracking branch 'origin' into document-highlights
johnfav03 Sep 24, 2025
9b512a5
added isWriteAccessForReference logic
johnfav03 Sep 24, 2025
ea32d43
cleaned up comments
johnfav03 Sep 24, 2025
d446a93
documenthighlights.go code and comment cleanup
johnfav03 Sep 24, 2025
1dcecaf
Update internal/fourslash/_scripts/convertFourslash.mts
johnfav03 Sep 24, 2025
0ec6d31
removed extraneous newline
johnfav03 Sep 24, 2025
2dd5588
minor test fix
johnfav03 Sep 25, 2025
f8c43e1
fixed out of bounds in findReferencedSymbolsForNode
johnfav03 Sep 25, 2025
e903fae
reupdated failing tests
johnfav03 Sep 25, 2025
016ab91
fixed ci check for convertfourslash
johnfav03 Sep 25, 2025
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
93 changes: 93 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,91 @@ type (
JsxAttributeList = NodeList // NodeList[*JsxAttributeLike]
)

func IsWriteOnlyAccess(node *Node) bool {
return accessKind(node) == AccessKindWrite
}

func IsWriteAccess(node *Node) bool {
return accessKind(node) != AccessKindRead
}

func accessKind(node *Node) AccessKind {
parent := node.Parent
switch parent.Kind {
case KindParenthesizedExpression:
return accessKind(parent)
case KindPrefixUnaryExpression:
operator := parent.AsPrefixUnaryExpression().Operator
if operator == KindPlusPlusToken || operator == KindMinusMinusToken {
return AccessKindReadWrite
}
return AccessKindRead
case KindPostfixUnaryExpression:
operator := parent.AsPostfixUnaryExpression().Operator
if operator == KindPlusPlusToken || operator == KindMinusMinusToken {
return AccessKindReadWrite
}
return AccessKindRead
case KindBinaryExpression:
if parent.AsBinaryExpression().Left == node {
operator := parent.AsBinaryExpression().OperatorToken
if IsAssignmentOperator(operator.Kind) {
if operator.Kind == KindEqualsToken {
return AccessKindWrite
}
return AccessKindReadWrite
}
}
return AccessKindRead
case KindPropertyAccessExpression:
if parent.AsPropertyAccessExpression().Name() != node {
return AccessKindRead
}
return accessKind(parent)
case KindPropertyAssignment:
parentAccess := accessKind(parent.Parent)
// In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write.
if node == parent.AsPropertyAssignment().Name() {
return reverseAccessKind(parentAccess)
}
return parentAccess
case KindShorthandPropertyAssignment:
// Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals.
if node == parent.AsShorthandPropertyAssignment().ObjectAssignmentInitializer {
return AccessKindRead
}
return accessKind(parent.Parent)
case KindArrayLiteralExpression:
return accessKind(parent)
case KindForInStatement, KindForOfStatement:
if node == parent.AsForInOrOfStatement().Initializer {
return AccessKindWrite
}
return AccessKindRead
}
return AccessKindRead
}

func reverseAccessKind(a AccessKind) AccessKind {
switch a {
case AccessKindRead:
return AccessKindWrite
case AccessKindWrite:
return AccessKindRead
case AccessKindReadWrite:
return AccessKindReadWrite
}
panic("Unhandled case in reverseAccessKind")
}

type AccessKind int32

const (
AccessKindRead AccessKind = iota // Only reads from a variable
AccessKindWrite // Only writes to a variable without ever reading it. E.g.: `x=1;`.
AccessKindReadWrite // Reads from and writes to a variable. E.g.: `f(x++);`, `x/=1`.
)

// DeclarationBase

type DeclarationBase struct {
Expand Down Expand Up @@ -3191,6 +3276,10 @@ func (node *ThrowStatement) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Expression)
}

func IsThrowStatement(node *Node) bool {
return node.Kind == KindThrowStatement
}

// TryStatement

type TryStatement struct {
Expand Down Expand Up @@ -6153,6 +6242,10 @@ func (node *YieldExpression) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Expression) | SubtreeContainsForAwaitOrAsyncGenerator
}

func IsYieldExpression(node *Node) bool {
return node.Kind == KindYieldExpression
}

// ArrowFunction

type ArrowFunction struct {
Expand Down
14 changes: 7 additions & 7 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10873,15 +10873,15 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
c.checkPropertyNotUsedBeforeDeclaration(prop, node, right)
c.markPropertyAsReferenced(prop, node, c.isSelfTypeAccess(left, parentSymbol))
c.symbolNodeLinks.Get(node).resolvedSymbol = prop
c.checkPropertyAccessibility(node, left.Kind == ast.KindSuperKeyword, isWriteAccess(node), apparentType, prop)
c.checkPropertyAccessibility(node, left.Kind == ast.KindSuperKeyword, ast.IsWriteAccess(node), apparentType, prop)
if c.isAssignmentToReadonlyEntity(node, prop, assignmentKind) {
c.error(right, diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, right.Text())
return c.errorType
}
switch {
case c.isThisPropertyAccessInConstructor(node, prop):
propType = c.autoType
case writeOnly || isWriteOnlyAccess(node):
case writeOnly || ast.IsWriteOnlyAccess(node):
propType = c.getWriteTypeOfSymbol(prop)
default:
propType = c.getTypeOfSymbol(prop)
Expand Down Expand Up @@ -13280,7 +13280,7 @@ func (c *Checker) getResolvedSymbol(node *ast.Node) *ast.Symbol {
var symbol *ast.Symbol
if !ast.NodeIsMissing(node) {
symbol = c.resolveName(node, node.AsIdentifier().Text, ast.SymbolFlagsValue|ast.SymbolFlagsExportValue,
c.getCannotFindNameDiagnosticForName(node), !isWriteOnlyAccess(node), false /*excludeGlobals*/)
c.getCannotFindNameDiagnosticForName(node), !ast.IsWriteOnlyAccess(node), false /*excludeGlobals*/)
}
links.resolvedSymbol = core.OrElse(symbol, c.unknownSymbol)
}
Expand Down Expand Up @@ -15715,9 +15715,9 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No
if ast.IsRightSideOfQualifiedNameOrPropertyAccess(location) {
location = location.Parent
}
if ast.IsExpressionNode(location) && (!ast.IsAssignmentTarget(location) || isWriteAccess(location)) {
if ast.IsExpressionNode(location) && (!ast.IsAssignmentTarget(location) || ast.IsWriteAccess(location)) {
var t *Type
if isWriteAccess(location) && location.Kind == ast.KindPropertyAccessExpression {
if ast.IsWriteAccess(location) && location.Kind == ast.KindPropertyAccessExpression {
t = c.checkPropertyAccessExpression(location, CheckModeNormal, true /*writeOnly*/)
} else {
t = c.getTypeOfExpression(location)
Expand All @@ -15735,7 +15735,7 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No
// to it at the given location. Since we have no control flow information for the
// hypothetical reference (control flow information is created and attached by the
// binder), we simply return the declared type of the symbol.
if isRightSideOfAccessExpression(location) && isWriteAccess(location.Parent) {
if isRightSideOfAccessExpression(location) && ast.IsWriteAccess(location.Parent) {
return c.getWriteTypeOfSymbol(symbol)
}
}
Expand Down Expand Up @@ -26707,7 +26707,7 @@ func (c *Checker) markPropertyAsReferenced(prop *ast.Symbol, nodeForCheckWriteOn
if !hasPrivateModifier && !hasPrivateIdentifier {
return
}
if nodeForCheckWriteOnly != nil && isWriteOnlyAccess(nodeForCheckWriteOnly) && prop.Flags&ast.SymbolFlagsSetAccessor == 0 {
if nodeForCheckWriteOnly != nil && ast.IsWriteOnlyAccess(nodeForCheckWriteOnly) && prop.Flags&ast.SymbolFlagsSetAccessor == 0 {
return
}
if isSelfTypeAccess {
Expand Down
85 changes: 0 additions & 85 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,91 +1074,6 @@ func isThisInitializedDeclaration(node *ast.Node) bool {
return node != nil && ast.IsVariableDeclaration(node) && node.AsVariableDeclaration().Initializer != nil && node.AsVariableDeclaration().Initializer.Kind == ast.KindThisKeyword
}

func isWriteOnlyAccess(node *ast.Node) bool {
return accessKind(node) == AccessKindWrite
}

func isWriteAccess(node *ast.Node) bool {
return accessKind(node) != AccessKindRead
}

type AccessKind int32

const (
AccessKindRead AccessKind = iota // Only reads from a variable
AccessKindWrite // Only writes to a variable without ever reading it. E.g.: `x=1;`.
AccessKindReadWrite // Reads from and writes to a variable. E.g.: `f(x++);`, `x/=1`.
)

func accessKind(node *ast.Node) AccessKind {
parent := node.Parent
switch parent.Kind {
case ast.KindParenthesizedExpression:
return accessKind(parent)
case ast.KindPrefixUnaryExpression:
operator := parent.AsPrefixUnaryExpression().Operator
if operator == ast.KindPlusPlusToken || operator == ast.KindMinusMinusToken {
return AccessKindReadWrite
}
return AccessKindRead
case ast.KindPostfixUnaryExpression:
operator := parent.AsPostfixUnaryExpression().Operator
if operator == ast.KindPlusPlusToken || operator == ast.KindMinusMinusToken {
return AccessKindReadWrite
}
return AccessKindRead
case ast.KindBinaryExpression:
if parent.AsBinaryExpression().Left == node {
operator := parent.AsBinaryExpression().OperatorToken
if ast.IsAssignmentOperator(operator.Kind) {
if operator.Kind == ast.KindEqualsToken {
return AccessKindWrite
}
return AccessKindReadWrite
}
}
return AccessKindRead
case ast.KindPropertyAccessExpression:
if parent.AsPropertyAccessExpression().Name() != node {
return AccessKindRead
}
return accessKind(parent)
case ast.KindPropertyAssignment:
parentAccess := accessKind(parent.Parent)
// In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write.
if node == parent.AsPropertyAssignment().Name() {
return reverseAccessKind(parentAccess)
}
return parentAccess
case ast.KindShorthandPropertyAssignment:
// Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals.
if node == parent.AsShorthandPropertyAssignment().ObjectAssignmentInitializer {
return AccessKindRead
}
return accessKind(parent.Parent)
case ast.KindArrayLiteralExpression:
return accessKind(parent)
case ast.KindForInStatement, ast.KindForOfStatement:
if node == parent.AsForInOrOfStatement().Initializer {
return AccessKindWrite
}
return AccessKindRead
}
return AccessKindRead
}

func reverseAccessKind(a AccessKind) AccessKind {
switch a {
case AccessKindRead:
return AccessKindWrite
case AccessKindWrite:
return AccessKindRead
case AccessKindReadWrite:
return AccessKindReadWrite
}
panic("Unhandled case in reverseAccessKind")
}

func isInfinityOrNaNString(name string) bool {
return name == "Infinity" || name == "-Infinity" || name == "NaN"
}
Expand Down
Loading
Loading