Skip to content

Commit

Permalink
Added cannotFindCallFuncFunction and many tests
Browse files Browse the repository at this point in the history
  • Loading branch information
markwpearce committed Nov 29, 2024
1 parent 9d1b21d commit 346e205
Show file tree
Hide file tree
Showing 15 changed files with 332 additions and 46 deletions.
11 changes: 11 additions & 0 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,17 @@ export let DiagnosticMessages = {
message: `Expected return statement in function`,
code: 1155,
severity: DiagnosticSeverity.Error
}),
cannotFindCallFuncFunction: (name: string, fullName: string, typeName: string) => ({
message: `Cannot find callfunc function '${name}' for type '${typeName}'`,
code: 1156,
data: {
name: name,
fullName: fullName,
typeName: typeName,
isCallfunc: true
},
severity: DiagnosticSeverity.Error
})
};
export const defaultMaximumTruncationLength = 160;
Expand Down
157 changes: 156 additions & 1 deletion src/Program.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type { SinonSpy } from 'sinon';
import { createSandbox } from 'sinon';
import { SymbolTypeFlag } from './SymbolTypeFlag';
import { AssetFile } from './files/AssetFile';
import type { ProvideFileEvent, CompilerPlugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeFileAddEvent, AfterFileAddEvent, BeforeFileRemoveEvent, AfterFileRemoveEvent } from './interfaces';
import type { ProvideFileEvent, CompilerPlugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeFileAddEvent, AfterFileAddEvent, BeforeFileRemoveEvent, AfterFileRemoveEvent, ScopeValidationOptions } from './interfaces';
import { StringType, TypedFunctionType, DynamicType, FloatType, IntegerType, InterfaceType, ArrayType, BooleanType, DoubleType, UnionType } from './types';
import { AssociativeArrayType } from './types/AssociativeArrayType';
import { ComponentType } from './types/ComponentType';
Expand Down Expand Up @@ -584,6 +584,161 @@ describe('Program', () => {
program.validate();
expectZeroDiagnostics(program);
});

describe('changed symbols', () => {
it('includes components when component interface changes', () => {
program.setFile('components/widget.xml', trim`
<component name="Widget" extends="Group">
<interface>
<field id="foo" type="string" />
</interface>
</component>
`);
program.setFile('components/other.xml', trim`
<component name="Other" extends="Group">
<interface>
<field id="foo" type="string" />
</interface>
</component>
`);
program.setFile('source/main.bs', `
sub sourceScopeFunc()
end sub
`);
program.validate();
let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;

expectZeroDiagnostics(program);
//change widget
program.setFile('components/widget.xml', trim`
<component name="Widget" extends="Group">
<interface>
<field id="foo" type="integer" />
</interface>
</component>
`);
program.validate();
expectZeroDiagnostics(program);
options = program['currentScopeValidationOptions'];
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
});

it('includes components when component callfunc changes', () => {
program.setFile('components/widget.xml', trim`
<component name="Widget" extends="Group">
<script type="text/brightscript" uri="widget.bs" />
<interface>
<function name="foo" />
</interface>
</component>
`);
program.setFile('components/widget.bs', `
sub foo()
end sub
`);
program.setFile('components/other.xml', trim`
<component name="Other" extends="Group">
<interface>
<field id="foo" type="string" />
</interface>
</component>
`);
program.setFile('source/main.bs', `
sub sourceScopeFunc()
end sub
`);
program.validate();
let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;

expectZeroDiagnostics(program);
//change [email protected]
program.setFile('components/widget.bs', `
sub foo(input)
print input
end sub
`);
program.validate();
expectZeroDiagnostics(program);
options = program['currentScopeValidationOptions'];
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
});

it('includes types that depend on a changed component', () => {
program.setFile('components/widget.xml', trim`
<component name="Widget" extends="Group">
<script type="text/brightscript" uri="widget.bs" />
<interface>
<function name="foo" />
</interface>
</component>
`);
program.setFile('components/widget.bs', `
sub foo()
end sub
`);
program.setFile('components/other.xml', trim`
<component name="Other" extends="Group">
<interface>
<field id="foo" type="string" />
</interface>
</component>
`);
program.setFile('source/main.bs', `
interface IncludesWidget
widget as roSGNodeWidget
end interface
sub sourceScopeFunc()
end sub
`);
program.validate();
expectZeroDiagnostics(program);
let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.true;

// change roSgNodeOther
program.setFile('components/other.xml', trim`
<component name="Other" extends="Group">
<interface>
<field id="foo" type="integer" />
</interface>
</component>
`);
program.validate();
expectZeroDiagnostics(program);
options = program['currentScopeValidationOptions'];

// only rosgnodewidget changes
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.false;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.false;

//change [email protected]
program.setFile('components/widget.bs', `
sub foo(input)
print input
end sub
`);
program.validate();
expectZeroDiagnostics(program);
options = program['currentScopeValidationOptions'];

// has rosgnodewidget AND IncludesWidget, because it depends on roSgnodeWidget
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.true;
});

});

});

describe('hasFile', () => {
Expand Down
53 changes: 36 additions & 17 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { CodeAction, Position, Range, SignatureInformation, Location, Docum
import type { BsConfig, FinalizedBsConfig } from './BsConfig';
import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent } from './interfaces';
import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent, ScopeValidationOptions } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DependencyGraph } from './DependencyGraph';
Expand Down Expand Up @@ -235,6 +235,7 @@ export class Program {

private fileSymbolInformation = new Map<string, { provides: ProvidedSymbolInfo; requires: UnresolvedSymbol[] }>();

private currentScopeValidationOptions: ScopeValidationOptions;

/**
* Map of typetime symbols which depend upon the key symbol
Expand Down Expand Up @@ -432,14 +433,16 @@ export class Program {
// There is a component that can be added - use it.
const componentScope = components[0].scope;

this.componentsTable.removeSymbol(symbolName);
componentScope.linkSymbolTable();
const componentType = componentScope.getComponentType();
if (componentType) {
this.componentsTable.addSymbol(symbolName, {}, componentType, SymbolTypeFlag.typetime);
}
const isComponentTypeDifferent = !previousComponentType || isReferenceType(previousComponentType) || !componentType.isEqual(previousComponentType);
const typeData = {};
const isSameAsPrevious = previousComponentType && componentType.isEqual(previousComponentType, typeData);
const isComponentTypeDifferent = !previousComponentType || isReferenceType(previousComponentType) || !isSameAsPrevious;
componentScope.unlinkSymbolTable();

return isComponentTypeDifferent;

}
Expand All @@ -458,15 +461,19 @@ export class Program {
return;
}
const components = this.components[componentKey] || [];
// Remove any existing symbols that match
this.componentsTable.removeSymbol(symbolName);
// There is a component that can be added - use it.
if (components.length > 0) {

const componentRefType = new ReferenceType(symbolName, symbolName, SymbolTypeFlag.typetime, () => this.componentsTable);
if (componentRefType) {
this.componentsTable.addSymbol(symbolName, {}, componentRefType, SymbolTypeFlag.typetime);
if (components.length > 0) {
// There is a component that can be added,
if (!this.componentsTable.hasSymbol(symbolName, SymbolTypeFlag.typetime)) {
// it doesn't already exist in the table
const componentRefType = new ReferenceType(symbolName, symbolName, SymbolTypeFlag.typetime, () => this.componentsTable);
if (componentRefType) {
this.componentsTable.addSymbol(symbolName, {}, componentRefType, SymbolTypeFlag.typetime);
}
}
} else {
// there is no component. remove from table
this.componentsTable.removeSymbol(symbolName);
}
}

Expand Down Expand Up @@ -823,6 +830,7 @@ export class Program {
//if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
let scope = this.scopes[file.destPath];
if (scope) {
this.logger.debug('Removing associated scope', scope.name);
const scopeDisposeEvent = {
program: this,
scope: scope
Expand All @@ -845,6 +853,8 @@ export class Program {
this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
}
if (isBrsFile(file)) {
this.logger.debug('Removing file symbol info', file.srcPath);

if (!keepSymbolInformation) {
this.fileSymbolInformation.delete(file.pkgPath);
}
Expand All @@ -853,8 +863,12 @@ export class Program {

//if this is a component, remove it from our components map
if (isXmlFile(file)) {
this.logger.debug('Unregistering component', file.srcPath);

this.unregisterComponent(file);
}
this.logger.debug('Disposing file', file.srcPath);

//dispose any disposable things on the file
for (const disposable of file?.disposables ?? []) {
disposable();
Expand Down Expand Up @@ -904,6 +918,9 @@ export class Program {
this.logger.time(LogLevel.info, ['Prebuild component types'], () => {
// cast a wide net for potential changes in components
for (const file of sortedFiles) {
if (file.isValidated) {
continue;
}
if (isXmlFile(file)) {
this.addDeferredComponentTypeSymbolCreation(file);
} else if (isBrsFile(file)) {
Expand Down Expand Up @@ -1021,7 +1038,8 @@ export class Program {
const changedTypeSymbols = changedSymbols.get(SymbolTypeFlag.typetime);
do {
foundDependentTypes = false;
for (const changedSymbol of changedTypeSymbols) {
const allChangedTypesSofar = [...Array.from(changedTypeSymbols), ...Array.from(dependentTypesChanged)];
for (const changedSymbol of allChangedTypesSofar) {
const symbolsDependentUponChangedSymbol = this.symbolDependencies.get(changedSymbol) ?? [];
for (const symbolName of symbolsDependentUponChangedSymbol) {
if (!changedTypeSymbols.has(symbolName) && !dependentTypesChanged.has(symbolName)) {
Expand Down Expand Up @@ -1059,6 +1077,12 @@ export class Program {
let validationTime = 0;
let scopesValidated = 0;
let changedFiles = new Set<BscFile>(afterValidateFiles);
this.currentScopeValidationOptions = {
filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
changedSymbols: changedSymbols,
changedFiles: changedFiles,
initialValidation: this.isFirstValidation
};
this.logger.time(LogLevel.info, ['Validate all scopes'], () => {
//sort the scope names so we get consistent results
const scopeNames = this.getSortedScopeNames();
Expand All @@ -1072,12 +1096,7 @@ export class Program {
}
for (let scopeName of scopeNames) {
let scope = this.scopes[scopeName];
const scopeValidated = scope.validate({
filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
changedSymbols: changedSymbols,
changedFiles: changedFiles,
initialValidation: this.isFirstValidation
});
const scopeValidated = scope.validate(this.currentScopeValidationOptions);
if (scopeValidated) {
scopesValidated++;
}
Expand Down
2 changes: 1 addition & 1 deletion src/bscPlugin/completions/CompletionsProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export class CompletionsProcessor {
const symbolNameLower = symbol?.name.toLowerCase();
let nameMatchesType = symbolNameLower === finalTypeNameLower;
if (isTypedFunctionType(type) && !nameMatchesType) {
if (symbolNameLower === type.name.replace(/\./gi, '_').toLowerCase()) {
if (symbolNameLower === type.name?.replace(/\./gi, '_').toLowerCase()) {
nameMatchesType = true;
}
}
Expand Down
Loading

0 comments on commit 346e205

Please sign in to comment.