Skip to content

Commit

Permalink
Updated function precedence for universal game to prioritize local fu…
Browse files Browse the repository at this point in the history
…nctions, then included functions, then built-in functions; if a function is defined multiple times, the first one found is used and others are ignored without error - unlike CoD2, where duplicate function names in local or included files cause an error.

Tests were reorganized, GscComplex renamed to GscAll with GscAll.UniversalGame and GscAll.CoD2
  • Loading branch information
eyza-cod2 committed Oct 20, 2024
1 parent 85f88de commit ac0f845
Show file tree
Hide file tree
Showing 52 changed files with 573 additions and 383 deletions.
3 changes: 2 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export default [
format: ["camelCase", "PascalCase"],
},
],
"@typescript-eslint/no-floating-promises": "error", // For TypeScript files
"@typescript-eslint/no-floating-promises": "error", // Error on not awaited promises
"@typescript-eslint/await-thenable": "warn", // Warn on awaited non-async function calls
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
Expand Down
6 changes: 3 additions & 3 deletions src/GscCompletionItemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider

// Get current function data
const functionGroup = gscData.functions.find(f => {
const range = f.scopeRange;
const range = f.rangeScope;
if (((position.line === range.start.line && position.character > range.start.character) || position.line > range.start.line) &&
((position.line === range.end.line && position.character < range.end.character) || position.line < range.end.line))
{
Expand Down Expand Up @@ -360,11 +360,11 @@ export class GscCompletionItemProvider implements vscode.CompletionItemProvider
// Uri is undefined in tests
if (gscFile !== undefined) {
// Local functions and included functions
const res = await GscFunctions.getFunctionReferenceState(undefined, gscFile);
const res = GscFunctions.getFunctionReferenceState(undefined, gscFile);

res.definitions.forEach(f => {
const item = new vscode.CompletionItem({label: f.func.name, description: "", detail: ""}, vscode.CompletionItemKind.Function);
item.documentation = f.func.generateMarkdownDescription(f.uri === gscFile.uri.toString(), f.uri, f.reason);
item.documentation = f.func.generateMarkdownDescription(f.uri.toString() === gscFile.uri.toString(), f.uri.toString(), f.reason);
completionItems.push(item);
});
}
Expand Down
7 changes: 7 additions & 0 deletions src/GscConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export type GscGameConfig = {
developerBlocks: boolean;
/** Allow /# #/ inside another /# #/ */
developerBlocksRecursive: boolean;
/** Allow duplicate function definitions */
duplicateFunctionDefinitions: boolean;


foreach: boolean;
doWhile: boolean;
Expand All @@ -60,6 +63,7 @@ export class GscConfig {
globalVariables: true,
developerBlocks: true,
developerBlocksRecursive: true,
duplicateFunctionDefinitions: true,
foreach: true,
doWhile: true,
arrayInitializer: true,
Expand All @@ -72,6 +76,7 @@ export class GscConfig {
globalVariables: false,
developerBlocks: false,
developerBlocksRecursive: false,
duplicateFunctionDefinitions: false,
foreach: false,
doWhile: true,
arrayInitializer: false,
Expand All @@ -84,6 +89,7 @@ export class GscConfig {
globalVariables: false,
developerBlocks: true,
developerBlocksRecursive: false,
duplicateFunctionDefinitions: false,
foreach: false,
doWhile: false,
arrayInitializer: false,
Expand All @@ -96,6 +102,7 @@ export class GscConfig {
globalVariables: false,
developerBlocks: true,
developerBlocksRecursive: false,
duplicateFunctionDefinitions: false,
foreach: false,
doWhile: false,
arrayInitializer: false,
Expand Down
8 changes: 3 additions & 5 deletions src/GscDefinitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ export class GscDefinitionProvider implements vscode.DefinitionProvider {
if (groupAtCursor.type === GroupType.FunctionName) {
const funcInfo = groupAtCursor.getFunctionReferenceInfo();
if (funcInfo !== undefined) {
const funcDefs = await GscFunctions.getAvailableFunctionsForFile(gscFile, funcInfo.name, funcInfo.path);
if (funcDefs !== undefined) {
funcDefs.forEach(f => {
locations.push(new vscode.Location(vscode.Uri.parse(f.uri), new vscode.Position(f.func.range.start.line, f.func.range.start.character)));
});
const funcDefs = GscFunctions.getFunctionDefinitions(gscFile, funcInfo);
if (funcDefs !== undefined && funcDefs.length > 0) {
locations.push(new vscode.Location(funcDefs[0].uri, funcDefs[0].func.rangeFunctionName));
}
}
}
Expand Down
92 changes: 62 additions & 30 deletions src/GscDiagnosticsCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { GscFiles } from './GscFiles';
import { GscFile } from './GscFile';
import { GroupType, GscData, GscGroup, GscToken, TokenType } from './GscFileParser';
import { CodFunctions } from './CodFunctions';
import { ConfigErrorDiagnostics, GscConfig, GscGame, GscGameRootFolder } from './GscConfig';
import { GscFunctions, GscFunctionState } from './GscFunctions';
import { ConfigErrorDiagnostics, GscConfig, GscGame } from './GscConfig';
import { GscFunctionDefinition, GscFunctions, GscFunctionState } from './GscFunctions';
import { assert } from 'console';
import { LoggerOutput } from './LoggerOutput';
import { Issues } from './Issues';
Expand Down Expand Up @@ -87,7 +87,7 @@ export class GscDiagnosticsCollection {
this.statusBarItem.tooltip = data.uri.toString();
}

count += await this.updateDiagnosticsForFile(data);
count += this.updateDiagnosticsForFile(data);

// Check if it's time to pause for UI update
const now = Date.now();
Expand Down Expand Up @@ -123,7 +123,7 @@ export class GscDiagnosticsCollection {
* @param gscFile The GSC file to generate diagnostics for.
* @returns The number of diagnostics created.
*/
public static async updateDiagnosticsForFile(gscFile: GscFile): Promise<number> {
public static updateDiagnosticsForFile(gscFile: GscFile): number {
try {
LoggerOutput.log("[GscDiagnosticsCollection] Creating diagnostics for file", vscode.workspace.asRelativePath(gscFile.uri));

Expand Down Expand Up @@ -151,18 +151,20 @@ export class GscDiagnosticsCollection {
walkGroupItems(gscFile.data.root, gscFile.data.root.items);


// Create diagnostic for function names
for (const d of groupFunctionNames) {
const diag = await GscDiagnosticsCollection.createDiagnosticsForFunctionName(d.group, gscFile);
// Create diagnostic for included files
const includedPaths = groupIncludedPaths.map(g => g.group.getTokensAsString());
for (let i = 0; i < groupIncludedPaths.length; i++) {
const d = groupIncludedPaths[i];
const path = d.group.getTokensAsString();
const diag = GscDiagnosticsCollection.createDiagnosticsForIncludedPaths(gscFile, d.group, path, includedPaths, i);
if (diag) {
gscFile.diagnostics.push(diag);
}
}

// Create diagnostic for included files
const includedPaths = groupIncludedPaths.map(g => g.group.getTokensAsString());
for (const d of groupIncludedPaths) {
const diag = GscDiagnosticsCollection.createDiagnosticsForIncludedPaths(d.group, gscFile, includedPaths);
// Create diagnostic for function names
for (const d of groupFunctionNames) {
const diag = GscDiagnosticsCollection.createDiagnosticsForFunctionName(d.group, gscFile);
if (diag) {
gscFile.diagnostics.push(diag);
}
Expand Down Expand Up @@ -370,53 +372,88 @@ export class GscDiagnosticsCollection {
}


private static createDiagnosticsForIncludedPaths(group: GscGroup, gscFile: GscFile, includedPaths: string[]): vscode.Diagnostic | undefined {
private static createDiagnosticsForIncludedPaths(gscFile: GscFile, group: GscGroup, path: string, allIncludedPaths: string[], index: number): vscode.Diagnostic | undefined {
assert(group.type === GroupType.Path);

const tokensAsPath = group.getTokensAsString();

const referenceData = GscFiles.getReferencedFileForFile(gscFile, tokensAsPath);
const referenceData = GscFiles.getReferencedFileForFile(gscFile, path);

if (!gscFile.config.gameConfig.includeFileItself && referenceData.gscFile?.uri.toString() === gscFile.uri.toString()) {
return new vscode.Diagnostic(group.getRange(), "File is including itself", vscode.DiagnosticSeverity.Error);
}

// Find how many times this file is included
let count = 0;
for (const includedPath of includedPaths) {
if (includedPath === tokensAsPath) {
for (const includedPath of allIncludedPaths) {
if (includedPath === path) {
count++;
}
if (count >= 2) {
return new vscode.Diagnostic(group.getRange(), "Duplicate #include file path", vscode.DiagnosticSeverity.Error);
}
}

// Check for duplicated function definitions
if (gscFile.config.gameConfig.duplicateFunctionDefinitions === false && referenceData.gscFile) {
// Get all function definitions from included file
const funcDefsInIncludedFile = GscFunctions.getLocalFunctionDefinitions(referenceData.gscFile);

const alreadyDefinedFunctions: GscFunctionDefinition[] = [];

const funcDefsFromLocalFile = GscFunctions.getLocalFunctionDefinitions(gscFile);
alreadyDefinedFunctions.push(...funcDefsFromLocalFile.map(f => ({func: f, uri: gscFile.uri, reason: "Local file"})));

for (let j = 0; j < index; j++) {
const otherPath = allIncludedPaths[j];
const otherReferenceData = GscFiles.getReferencedFileForFile(gscFile, otherPath);
if (!otherReferenceData.gscFile) {
continue;
}
const otherFuncDefs = GscFunctions.getLocalFunctionDefinitions(otherReferenceData.gscFile);

alreadyDefinedFunctions.push(...otherFuncDefs.map(f => ({func: f, uri: otherReferenceData.gscFile!.uri, reason: "Included file"})));
}

for (const funcDef of funcDefsInIncludedFile) {
for (const alreadyDefinedFunction of alreadyDefinedFunctions) {
if (funcDef.nameId === alreadyDefinedFunction.func.nameId) {
const s = alreadyDefinedFunction.reason === "Local file" ? "this file" : "included file '" + vscode.workspace.asRelativePath(alreadyDefinedFunction.uri) + "'";
return new vscode.Diagnostic(group.getRange(), `Function '${funcDef.name}' is already defined in ${s}!`, vscode.DiagnosticSeverity.Error);
}
}
}
}

if (referenceData.gscFile === undefined) {

// This file path is ignored by configuration
if (gscFile.config.ignoredFilePaths.some(ignoredPath => tokensAsPath.toLowerCase().startsWith(ignoredPath.toLowerCase()))) {
if (gscFile.config.ignoredFilePaths.some(ignoredPath => path.toLowerCase().startsWith(ignoredPath.toLowerCase()))) {
return;
}
const d = new vscode.Diagnostic(
group.getRange(),
`File '${tokensAsPath}.gsc' was not found in workspace folder ${gscFile.config.referenceableGameRootFolders.map(f => "'" + f.relativePath + "'").join(", ")}`,
`File '${path}.gsc' was not found in workspace folder ${gscFile.config.referenceableGameRootFolders.map(f => "'" + f.relativePath + "'").join(", ")}`,
vscode.DiagnosticSeverity.Error);
d.code = "unknown_file_path_" + tokensAsPath;
d.code = "unknown_file_path_" + path;
return d;
}

}


private static async createDiagnosticsForFunctionName(
private static createDiagnosticsForFunctionName(
group: GscGroup, gscFile: GscFile) {

// Function declaration
if (group.parent?.type === GroupType.FunctionDeclaration) {
// TODO check for duplicate


const funcName = group.getFirstToken().name;

// Check for duplicate function name definition
const defs = GscFunctions.getLocalFunctionDefinitions(gscFile, funcName);
if (defs.length > 1) {
return new vscode.Diagnostic(group.getRange(), `Duplicate function definition of '${funcName}'!`, vscode.DiagnosticSeverity.Error);
}


// This function is overwriting the build-in function
if (CodFunctions.isPredefinedFunction(funcName, gscFile.config.currentGame)) {
Expand All @@ -430,13 +467,13 @@ export class GscDiagnosticsCollection {
const funcInfo = group.getFunctionReferenceInfo();
if (funcInfo !== undefined) {

const res = await GscFunctions.getFunctionReferenceState({name: funcInfo.name, path: funcInfo.path}, gscFile);
const res = GscFunctions.getFunctionReferenceState({name: funcInfo.name, path: funcInfo.path}, gscFile);

switch (res.state as GscFunctionState) {
case GscFunctionState.NameIgnored:
return;

// Function was found in exactly one place
// Function was found
case GscFunctionState.Found:
const funcDef = res.definitions[0].func;
if (funcInfo.params.length > funcDef.parameters.length) {
Expand All @@ -451,11 +488,6 @@ export class GscDiagnosticsCollection {
break;


// Function is defined on too many places
case GscFunctionState.FoundOnMultiplePlaces:
return new vscode.Diagnostic(group.getRange(), `Function '${funcInfo.name}' is defined in ${res.definitions.length} places!`, vscode.DiagnosticSeverity.Error);


// This function is predefined function
case GscFunctionState.FoundInPredefined:
// Find in predefined functions
Expand Down
1 change: 1 addition & 0 deletions src/GscFileParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,7 @@ export class GscFileParser {
paramTokens,
[],
innerGroup.getRange(),
innerGroup.items[0].items[0].getRange(),
innerGroup.items[1].getRange()
);

Expand Down
2 changes: 1 addition & 1 deletion src/GscFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ export class GscFiles {
LoggerOutput.log("Debouncing done (" + debounceTime + "ms elapsed) - diagnostics update for file (" + debugText + ")", vscode.workspace.asRelativePath(uri!));
this.debounceTimersDiagnostics.delete(uriString);

void GscDiagnosticsCollection.updateDiagnosticsForFile(gscFile);
GscDiagnosticsCollection.updateDiagnosticsForFile(gscFile);
}, debounceTime));
} else {
this.debounceTimersDiagnostics.set("", setTimeout(() => {
Expand Down
Loading

0 comments on commit ac0f845

Please sign in to comment.