Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
153 changes: 153 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,159 @@ func IsWriteAccess(node *Node) bool {
return accessKind(node) != AccessKindRead
}

func IsWriteAccessForReference(node *Node) bool {
decl := getDeclarationFromName(node)
return (decl != nil && declarationIsWriteAccess(decl)) || node.Kind == KindDefaultKeyword || IsWriteAccess(node)
}

func getDeclarationFromName(name *Node) *Declaration {
if name == nil || name.Parent == nil {
return nil
}
parent := name.Parent
switch name.Kind {
case KindStringLiteral, KindNoSubstitutionTemplateLiteral, KindNumericLiteral:
if IsComputedPropertyName(parent) {
return parent.Parent
}
fallthrough
case KindIdentifier:
if IsDeclaration(parent) {
if parent.Name() == name {
return parent
}
return nil
}
if IsQualifiedName(parent) {
tag := parent.Parent
if IsJSDocParameterTag(tag) && tag.Name() == parent {
return tag
}
return nil
}
binExp := parent.Parent
if IsBinaryExpression(binExp) && GetAssignmentDeclarationKind(binExp.AsBinaryExpression()) != JSDeclarationKindNone {
// (binExp.left as BindableStaticNameExpression).symbol || binExp.symbol
leftHasSymbol := false
if binExp.AsBinaryExpression().Left != nil && binExp.AsBinaryExpression().Left.Symbol() != nil {
leftHasSymbol = true
}
if leftHasSymbol || binExp.Symbol() != nil {
if GetNameOfDeclaration(binExp.AsNode()) == name {
return binExp.AsNode()
}
}
}
case KindPrivateIdentifier:
if IsDeclaration(parent) && parent.Name() == name {
return parent
}
}
return nil
}

func declarationIsWriteAccess(decl *Node) bool {
if decl == nil {
return false
}
// Consider anything in an ambient declaration to be a write access since it may be coming from JS.
if decl.Flags&NodeFlagsAmbient != 0 {
return true
}

switch decl.Kind {
case KindBinaryExpression,
KindBindingElement,
KindClassDeclaration,
KindClassExpression,
KindDefaultKeyword,
KindEnumDeclaration,
KindEnumMember,
KindExportSpecifier,
KindImportClause, // default import
KindImportEqualsDeclaration,
KindImportSpecifier,
KindInterfaceDeclaration,
KindJSDocCallbackTag,
KindJSDocTypedefTag,
KindJsxAttribute,
KindModuleDeclaration,
KindNamespaceExportDeclaration,
KindNamespaceImport,
KindNamespaceExport,
KindParameter,
KindShorthandPropertyAssignment,
KindTypeAliasDeclaration,
KindTypeParameter:
return true

case KindPropertyAssignment:
// In `({ x: y } = 0);`, `x` is not a write access.
return !IsArrayLiteralOrObjectLiteralDestructuringPattern(decl.Parent)

case KindFunctionDeclaration, KindFunctionExpression, KindConstructor, KindMethodDeclaration, KindGetAccessor, KindSetAccessor:
// functions considered write if they provide a value (have a body)
switch decl.Kind {
case KindFunctionDeclaration:
return decl.AsFunctionDeclaration().Body != nil
case KindFunctionExpression:
return decl.AsFunctionExpression().Body != nil
case KindConstructor:
// constructor node stores body on the parent? treat same as others
return decl.AsConstructorDeclaration().Body != nil
case KindMethodDeclaration:
return decl.AsMethodDeclaration().Body != nil
case KindGetAccessor:
return decl.AsGetAccessorDeclaration().Body != nil
case KindSetAccessor:
return decl.AsSetAccessorDeclaration().Body != nil
}
return false

case KindVariableDeclaration, KindPropertyDeclaration:
// variable/property write if initializer present or is in catch clause
var hasInit bool
switch decl.Kind {
case KindVariableDeclaration:
hasInit = decl.AsVariableDeclaration().Initializer != nil
case KindPropertyDeclaration:
hasInit = decl.AsPropertyDeclaration().Initializer != nil
}
return hasInit || IsCatchClause(decl.Parent)

case KindMethodSignature, KindPropertySignature, KindJSDocPropertyTag, KindJSDocParameterTag:
return false

default:
// preserve TS behavior: crash on unexpected kinds
panic("Unhandled case in declarationIsWriteAccess")
}
}

func IsArrayLiteralOrObjectLiteralDestructuringPattern(node *Node) bool {
if !(IsArrayLiteralExpression(node) || IsObjectLiteralExpression(node)) {
return false
}
parent := node.Parent
// [a,b,c] from:
// [a, b, c] = someExpression;
if IsBinaryExpression(parent) && parent.AsBinaryExpression().Left == node && parent.AsBinaryExpression().OperatorToken.Kind == KindEqualsToken {
return true
}
// [a, b, c] from:
// for([a, b, c] of expression)
if IsForOfStatement(parent) && parent.Initializer() == node {
return true
}
// {x, a: {a, b, c} } = someExpression
if IsPropertyAssignment(parent) {
return IsArrayLiteralOrObjectLiteralDestructuringPattern(parent.Parent)
}
// [a, b, c] of
// [x, [a, b, c] ] = someExpression
return IsArrayLiteralOrObjectLiteralDestructuringPattern(parent)
}

func accessKind(node *Node) AccessKind {
parent := node.Parent
switch parent.Kind {
Expand Down
26 changes: 1 addition & 25 deletions internal/checker/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ func (c *Checker) GetPropertySymbolsFromContextualType(node *ast.Node, contextua
//
// [a] = [ property1, property2 ]
func (c *Checker) GetPropertySymbolOfDestructuringAssignment(location *ast.Node) *ast.Symbol {
if isArrayLiteralOrObjectLiteralDestructuringPattern(location.Parent.Parent) {
if ast.IsArrayLiteralOrObjectLiteralDestructuringPattern(location.Parent.Parent) {
// Get the type of the object or array literal and then look for property of given name in the type
if typeOfObjectLiteral := c.getTypeOfAssignmentPattern(location.Parent.Parent); typeOfObjectLiteral != nil {
return c.getPropertyOfType(typeOfObjectLiteral, location.Text())
Expand Down Expand Up @@ -809,27 +809,3 @@ func (c *Checker) getTypeOfAssignmentPattern(expr *ast.Node) *Type {
elementType := core.OrElse(c.checkIteratedTypeOrElementType(IterationUseDestructuring, typeOfArrayLiteral, c.undefinedType, expr.Parent), c.errorType)
return c.checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, slices.Index(node.AsArrayLiteralExpression().Elements.Nodes, expr), elementType, CheckModeNormal)
}

func isArrayLiteralOrObjectLiteralDestructuringPattern(node *ast.Node) bool {
if !(ast.IsArrayLiteralExpression(node) || ast.IsObjectLiteralExpression(node)) {
return false
}
parent := node.Parent
// [a,b,c] from:
// [a, b, c] = someExpression;
if ast.IsBinaryExpression(parent) && parent.AsBinaryExpression().Left == node && parent.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken {
return true
}
// [a, b, c] from:
// for([a, b, c] of expression)
if ast.IsForOfStatement(parent) && parent.Initializer() == node {
return true
}
// {x, a: {a, b, c} } = someExpression
if ast.IsPropertyAssignment(parent) {
return isArrayLiteralOrObjectLiteralDestructuringPattern(parent.Parent)
}
// [a, b, c] of
// [x, [a, b, c] ] = someExpression
return isArrayLiteralOrObjectLiteralDestructuringPattern(parent)
}
59 changes: 6 additions & 53 deletions internal/fourslash/_scripts/convertFourslash.mts
Original file line number Diff line number Diff line change
Expand Up @@ -787,17 +787,17 @@ function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [V
let strArg;
if (strArg = getArrayLiteralExpression(arg)) {
for (const elem of strArg.elements) {
const newArg = parseBaselineDocumentHighlightsArg(elem);
const newArg = parseBaselineMarkerOrRangeArg(elem);
if (!newArg) {
return undefined;
}
newArgs.push(newArg);
}
}
else if (ts.isObjectLiteralExpression(arg)) {
// User preferences case, but multiple files isn't implemented in corsa yet
// !! todo when multiple files supported in lsp
}
else if (strArg = parseBaselineDocumentHighlightsArg(arg)) {
else if (strArg = parseBaselineMarkerOrRangeArg(arg)) {
newArgs.push(strArg);
}
else {
Expand All @@ -817,53 +817,6 @@ function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [V
}];
}

function parseBaselineDocumentHighlightsArg(arg: ts.Expression): string | undefined {
if (ts.isStringLiteral(arg)) {
return getGoStringLiteral(arg.text);
}
else if (ts.isIdentifier(arg) || (ts.isElementAccessExpression(arg) && ts.isIdentifier(arg.expression))) {
const argName = ts.isIdentifier(arg) ? arg.text : (arg.expression as ts.Identifier).text;
const file = arg.getSourceFile();
const varStmts = file.statements.filter(ts.isVariableStatement);
for (const varStmt of varStmts) {
for (const decl of varStmt.declarationList.declarations) {
if (ts.isArrayBindingPattern(decl.name) && decl.initializer?.getText().includes("ranges")) {
for (let i = 0; i < decl.name.elements.length; i++) {
const elem = decl.name.elements[i];
if (ts.isBindingElement(elem) && ts.isIdentifier(elem.name) && elem.name.text === argName) {
// `const [range_0, ..., range_n, ...] = test.ranges();` and arg is `range_n`
if (elem.dotDotDotToken === undefined) {
return `f.Ranges()[${i}]`;
}
// `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest[n]`
if (ts.isElementAccessExpression(arg)) {
return `f.Ranges()[${i + parseInt(arg.argumentExpression!.getText())}]`;
}
// `const [range_0, ..., ...rest] = test.ranges();` and arg is `rest`
return `ToAny(f.Ranges()[${i}:])...`;
}
}
}
}
}
const init = getNodeOfKind(arg, ts.isCallExpression);
if (init) {
const result = getRangesByTextArg(init);
if (result) {
return result;
}
}
}
else if (ts.isCallExpression(arg)) {
const result = getRangesByTextArg(arg);
if (result) {
return result;
}
}
console.error(`Unrecognized argument in verify.baselineRename: ${arg.getText()}`);
return undefined;
}

function parseBaselineGoToDefinitionArgs(args: readonly ts.Expression[]): [VerifyBaselineGoToDefinitionCmd] | undefined {
const newArgs = [];
for (const arg of args) {
Expand Down Expand Up @@ -929,7 +882,7 @@ function parseBaselineRenameArgs(funcName: string, args: readonly ts.Expression[
let typedArg;
if ((typedArg = getArrayLiteralExpression(arg))) {
for (const elem of typedArg.elements) {
const newArg = parseBaselineRenameArg(elem);
const newArg = parseBaselineMarkerOrRangeArg(elem);
if (!newArg) {
return undefined;
}
Expand All @@ -944,7 +897,7 @@ function parseBaselineRenameArgs(funcName: string, args: readonly ts.Expression[
}
continue;
}
else if (typedArg = parseBaselineRenameArg(arg)) {
else if (typedArg = parseBaselineMarkerOrRangeArg(arg)) {
newArgs.push(typedArg);
}
else {
Expand Down Expand Up @@ -982,7 +935,7 @@ function parseUserPreferences(arg: ts.ObjectLiteralExpression): string | undefin
return `&ls.UserPreferences{${preferences.join(",")}}`;
}

function parseBaselineRenameArg(arg: ts.Expression): string | undefined {
function parseBaselineMarkerOrRangeArg(arg: ts.Expression): string | undefined {
if (ts.isStringLiteral(arg)) {
return getGoStringLiteral(arg.text);
}
Expand Down
Loading
Loading