From 1bc87988883bdbc2a34dc62520d370dd97b1fc27 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 2 Oct 2026 16:11:05 -0400 Subject: [PATCH] Only build the shadowed-variable range when reporting it detectShadowedLocalVar() is called for every assignment, for each item and parameter during scope validation, but the name range is only used when a diagnostic is added. Pass the name token and resolve the range on demand. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/bscPlugin/validation/ScopeValidator.ts | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts index bedcb211b..e3f37384f 100644 --- a/src/bscPlugin/validation/ScopeValidator.ts +++ b/src/bscPlugin/validation/ScopeValidator.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import { DiagnosticTag, type Range } from 'vscode-languageserver'; +import { DiagnosticTag } from 'vscode-languageserver'; import { isAliasStatement, isArrayType, isAssignmentStatement, isAssociativeArrayType, isBinaryExpression, isBooleanTypeLike, isBrsFile, isCallExpression, isCallFuncableTypeLike, isCallableType, isCallfuncExpression, isClassStatement, isClassType, isComponentType, isCompoundType, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionParameterExpression, isIterableType, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isNumberTypeLike, isObjectType, isPrimitiveType, isReferenceType, isReturnStatement, isStringTypeLike, isTypeStatementType, isTypedFunctionType, isUnionType, isVariableExpression, isVoidType, isXmlScope } from '../../astUtils/reflection'; import type { DiagnosticInfo } from '../../DiagnosticMessages'; import { DiagnosticMessages } from '../../DiagnosticMessages'; @@ -10,7 +10,7 @@ import type { AssignmentStatement, AugmentedAssignmentStatement, ClassStatement, import { util } from '../../util'; import { nodes, components } from '../../roku-types'; import type { BRSComponentData } from '../../roku-types'; -import type { Token } from '../../lexer/Token'; +import type { Locatable, Token } from '../../lexer/Token'; import { AstNodeKind } from '../../parser/AstNode'; import type { AstNode } from '../../parser/AstNode'; import type { Expression } from '../../parser/AstNode'; @@ -241,7 +241,7 @@ export class ScopeValidator { expr: assignStmt, name: assignStmt.tokens.name.text, type: this.getNodeTypeWrapper(file, assignStmt, { flags: SymbolTypeFlag.runtime }), - nameRange: util.getLocation(assignStmt.tokens.name)?.range + nameToken: assignStmt.tokens.name }); }); }, @@ -266,7 +266,7 @@ export class ScopeValidator { expr: forEachStmt, name: forEachStmt.tokens.item.text, type: this.getNodeTypeWrapper(file, forEachStmt, { flags: SymbolTypeFlag.runtime }), - nameRange: util.getLocation(forEachStmt.tokens.item)?.range + nameToken: forEachStmt.tokens.item }); this.validateForEachStatement(file, forEachStmt); }); @@ -277,7 +277,7 @@ export class ScopeValidator { expr: funcParam, name: funcParam.tokens.name.text, type: this.getNodeTypeWrapper(file, funcParam, { flags: SymbolTypeFlag.runtime }), - nameRange: util.getLocation(funcParam.tokens.name)?.range + nameToken: funcParam.tokens.name }); }); }, @@ -1558,7 +1558,10 @@ export class ScopeValidator { } } - public detectShadowedLocalVar(file: BrsFile, varDeclaration: { expr: AstNode; name: string; type: BscType; nameRange: Range }) { + /** + * Detect a local variable that shadows a function or class. `varDeclaration.nameToken` is only resolved into a range when a diagnostic is actually added + */ + public detectShadowedLocalVar(file: BrsFile, varDeclaration: { expr: AstNode; name: string; type: BscType; nameToken: Locatable }) { const varName = varDeclaration.name; const lowerVarName = varName.toLowerCase(); const callableContainerMap = this.event.scope.getCallableContainerMap(); @@ -1577,7 +1580,7 @@ export class ScopeValidator { if (varIsFunction()) { this.addMultiScopeDiagnostic({ ...DiagnosticMessages.localVarFunctionShadowsParentFunction('stdlib'), - location: util.createLocationFromFileRange(file, varDeclaration.nameRange) + location: util.createLocationFromFileRange(file, util.getLocation(varDeclaration.nameToken)?.range) }); } } else if (callableContainerMap.has(lowerVarName) && !localVarIsInNamespace) { @@ -1586,7 +1589,7 @@ export class ScopeValidator { if (varIsFunction()) { this.addMultiScopeDiagnostic({ ...DiagnosticMessages.localVarFunctionShadowsParentFunction('scope'), - location: util.createLocationFromFileRange(file, varDeclaration.nameRange), + location: util.createLocationFromFileRange(file, util.getLocation(varDeclaration.nameToken)?.range), relatedInformation: [{ message: 'Function declared here', location: util.createLocationFromFileRange( @@ -1598,7 +1601,7 @@ export class ScopeValidator { } else { this.addMultiScopeDiagnostic({ ...DiagnosticMessages.localVarShadowedByScopedFunction(), - location: util.createLocationFromFileRange(file, varDeclaration.nameRange), + location: util.createLocationFromFileRange(file, util.getLocation(varDeclaration.nameToken)?.range), relatedInformation: [{ message: 'Function declared here', location: util.createLocationFromRange( @@ -1614,7 +1617,7 @@ export class ScopeValidator { if (classStmtLink) { this.addMultiScopeDiagnostic({ ...DiagnosticMessages.localVarShadowedByScopedFunction(), - location: util.createLocationFromFileRange(file, varDeclaration.nameRange), + location: util.createLocationFromFileRange(file, util.getLocation(varDeclaration.nameToken)?.range), relatedInformation: [{ message: 'Class declared here', location: util.createLocationFromRange(