Skip to content

Commit e793424

Browse files
authored
Merge pull request #94 from SSlinky/dev
Bug fix scope cleaning
2 parents 33d0ad6 + 6fdc932 commit e793424

File tree

3 files changed

+63
-34
lines changed

3 files changed

+63
-34
lines changed

server/src/capabilities/capabilities.ts

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { FoldingRange, FoldingRangeKind } from '../capabilities/folding';
1717
import { SemanticToken, SemanticTokenModifiers, SemanticTokenTypes } from '../capabilities/semanticTokens';
1818
import { BaseRuleSyntaxElement, BaseIdentifyableSyntaxElement, BaseSyntaxElement, Context, HasSemanticTokenCapability } from '../project/elements/base';
1919
import { AmbiguousNameDiagnostic, BaseDiagnostic, DuplicateDeclarationDiagnostic, ShadowDeclarationDiagnostic, SubOrFunctionNotDefinedDiagnostic, UnusedDiagnostic, VariableNotDefinedDiagnostic } from './diagnostics';
20-
import { isPositionInsideRange, isRangeInsideRange } from '../utils/helpers';
20+
import { isPositionInsideRange, isRangeInsideRange, rangeEquals } from '../utils/helpers';
2121

2222

2323
abstract class BaseCapability {
@@ -266,6 +266,10 @@ export class ScopeItemCapability {
266266
return result === '' ? 'NONE' : result;
267267
}
268268

269+
get range(): Range | undefined {
270+
return this.element?.context.range;
271+
}
272+
269273
// Item Properties
270274
locationUri?: string;
271275
isPublicScope?: boolean;
@@ -287,24 +291,7 @@ export class ScopeItemCapability {
287291
if (this.type === ScopeType.REFERENCE) {
288292
// Link to declaration if it exists.
289293
this.resolveLinks();
290-
if (!this.link) {
291-
// TODO:
292-
// References to variables should get a diagnostic if they aren't declared.
293-
// -- No option explicit: Hint with code action to declare.
294-
// GET before declared gets a warning.
295-
// -- Option explicit: Error with code action to declare.
296-
// -- Subsequent explicit declaration should raise duplicate declaration (current bahaviour).
297-
// -- All declarations with no GET references get a warning.
298-
// References to function or sub calls should raise an error if they aren't declared.
299-
// -- Must always throw even when option explicit not present.
300-
// -- Nothing required on first reference as declaration may come later.
301-
const severity = this.isOptionExplicitScope
302-
? DiagnosticSeverity.Error
303-
: DiagnosticSeverity.Hint;
304-
const _ = this.assignmentType & AssignmentType.CALL
305-
? this.pushDiagnostic(SubOrFunctionNotDefinedDiagnostic, this, this.name)
306-
: this.pushDiagnostic(VariableNotDefinedDiagnostic, this, this.name, severity);
307-
}
294+
this.validateLink();
308295
} else {
309296
// Diagnostic checks on declarations.
310297
const ancestors = this.getParentChain();
@@ -546,6 +533,27 @@ export class ScopeItemCapability {
546533
this.linkThisToItem(foundDeclarations[0]);
547534
}
548535

536+
private validateLink() {
537+
if (!this.link) {
538+
// TODO:
539+
// References to variables should get a diagnostic if they aren't declared.
540+
// -- No option explicit: Hint with code action to declare.
541+
// GET before declared gets a warning.
542+
// -- Option explicit: Error with code action to declare.
543+
// -- Subsequent explicit declaration should raise duplicate declaration (current bahaviour).
544+
// -- All declarations with no GET references get a warning.
545+
// References to function or sub calls should raise an error if they aren't declared.
546+
// -- Must always throw even when option explicit not present.
547+
// -- Nothing required on first reference as declaration may come later.
548+
const severity = this.isOptionExplicitScope
549+
? DiagnosticSeverity.Error
550+
: DiagnosticSeverity.Hint;
551+
const _ = this.assignmentType & AssignmentType.CALL
552+
? this.pushDiagnostic(SubOrFunctionNotDefinedDiagnostic, this, this.name)
553+
: this.pushDiagnostic(VariableNotDefinedDiagnostic, this, this.name, severity);
554+
}
555+
}
556+
549557
private linkThisToItem(linkItem?: ScopeItemCapability): void {
550558
if (!linkItem) {
551559
return;
@@ -787,28 +795,23 @@ export class ScopeItemCapability {
787795
* Recursively removes all scopes with the passed in uri and
788796
* within the range bounds, including where it is linked.
789797
*/
790-
invalidate(uri: string, range: Range): void {
791-
const isInvalidScope = (scope: ScopeItemCapability) =>
792-
scope.locationUri === uri
793-
&& scope.element?.context.range
794-
&& isRangeInsideRange(scope.element.context.range, range);
795-
798+
invalidate(uri: string, range?: Range): void {
796799
const cleanScopes = (scopes?: ScopeItemCapability[]) => {
797800
if (scopes === undefined) {
798801
return undefined;
799802
}
800803

801804
const result: ScopeItemCapability[] = [];
802805
scopes.forEach(scope => {
803-
if (isInvalidScope(scope)) {
806+
if (scope.isLocatedAt(uri, range)) {
804807
Services.logger.debug(`Invalidating ${scope.name}`);
805808

806809
// Clean the backlinks on the linked item if we have one.
807810
if (scope.link) scope.link.backlinks = cleanScopes(
808811
scope.link.backlinks);
809812

810813
// Clean the invaludated scope.
811-
scope.invalidate(uri, range);
814+
scope.invalidate(uri, scope.range);
812815

813816
return;
814817
}
@@ -849,10 +852,24 @@ export class ScopeItemCapability {
849852

850853
// Do a basic clean on backlinks that doesn't trigger recursion.
851854
if (this.backlinks) {
852-
this.backlinks = this.backlinks.filter(scope => !isInvalidScope(scope));
855+
this.backlinks = this.backlinks
856+
.filter(scope => !scope.isLocatedAt(uri, range));
853857
}
854858
}
855859

860+
/** Returns true if the uri matches and, if passed, range is fully inside range. */
861+
isLocatedAt(uri: string, range?: Range): boolean {
862+
if (uri !== this.locationUri) {
863+
return false;
864+
}
865+
866+
if (!range) {
867+
return true;
868+
}
869+
870+
return isRangeInsideRange(this.range, range);
871+
}
872+
856873
/** Returns true for public and false for private */
857874
private getVisibility(item: ScopeItemCapability): boolean {
858875
// Classes and modules are always public.
@@ -900,7 +917,7 @@ export class ScopeItemCapability {
900917

901918
// Check all items for whether they have a name overlap or a scope overlap.
902919
scope?.maps.forEach(map => map.forEach(items => items.forEach(item => {
903-
const elementRange = item.element?.context.range;
920+
const elementRange = item.range;
904921
const identifierRange = item.element?.identifierCapability?.range;
905922
if (identifierRange && isPositionInsideRange(position, identifierRange)) {
906923
// Position is inside the identifier, push to results.
@@ -972,7 +989,7 @@ export class ScopeItemCapability {
972989
link.locationUri,
973990
link.element.context.range,
974991
link.element.identifierCapability.range,
975-
this.element?.context.range
992+
this.range
976993
);
977994
}
978995

@@ -1045,7 +1062,7 @@ export class ScopeItemCapability {
10451062
};
10461063

10471064
const m = new Map<string, ScopeItemCapability>();
1048-
results.forEach(s => m.set(rangeString(s.element?.context.range), s));
1065+
results.forEach(s => m.set(rangeString(s.range), s));
10491066
return Array.from(m.values());
10501067
}
10511068
}

server/src/project/workspace.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ export class Workspace implements IWorkspace {
153153

154154
if (previousDocument) {
155155
Services.projectScope.invalidate(
156-
previousDocument.uri,
157-
previousDocument.range
156+
previousDocument.uri
158157
);
159158
}
160159

server/src/utils/helpers.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,12 @@ export function isPositionInsideRange(position: Position, range: Range): boolean
9494
* @param inner The range to test as enveloped.
9595
* @param outer The range to test as enveloping.
9696
*/
97-
export function isRangeInsideRange(inner: Range, outer: Range): boolean {
97+
export function isRangeInsideRange(inner?: Range, outer?: Range): boolean {
98+
// Test we have ranges.
99+
if (!inner || !outer) {
100+
return false;
101+
}
102+
98103
// Test characters on single-line ranges.
99104
const isSingleLine = inner.start.line === inner.end.line
100105
&& outer.start.line === outer.end.line
@@ -107,4 +112,12 @@ export function isRangeInsideRange(inner: Range, outer: Range): boolean {
107112
// Test lines on multi-line ranges.
108113
return inner.start.line >= outer.start.line
109114
&& inner.end.line <= outer.end.line;
115+
}
116+
117+
export function rangeEquals(r1?: Range, r2?: Range): boolean {
118+
return !!r1 && !!r2
119+
&& r1.start.line === r2.start.line
120+
&& r1.start.character === r2.start.character
121+
&& r1.end.line === r2.end.line
122+
&& r1.end.character === r2.end.character;
110123
}

0 commit comments

Comments
 (0)