Rewrite imports handling (#73)

This commit is contained in:
InversionSpaces 2023-12-20 12:57:14 +01:00 committed by GitHub
parent 60bd5335b5
commit 3fad69ba77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 140 additions and 23 deletions

View File

@ -63,9 +63,48 @@
"title": "Aqua",
"properties": {
"aquaSettings.imports": {
"definitions": {
"legacyImports": {
"description": "Legacy format of imports - just an array of paths",
"type": "array",
"items": {
"type": "string"
}
},
"structuredImports": {
"description": "Structured format of imports - dict of settings for path prefixes",
"type": "object",
"additionalProperties": {
"description": "Settings for path prefix - dict of locations for import prefixes",
"type": "object",
"additionalProperties": {
"description": "Location for import prefix",
"type": [
"string",
"array"
],
"items": {
"type": "string"
}
}
}
}
},
"scope": "resource",
"type": "array",
"default": [],
"type": [
"object",
"array"
],
"if": {
"type": "object"
},
"then": {
"$ref": "#/definitions/structuredImports"
},
"else": {
"$ref": "#/definitions/legacyImports"
},
"default": {},
"description": "Adds imports for aqua file or project"
},
"aquaSettings.enableLegacyAutoImportSearch": {
@ -115,4 +154,4 @@
"prettier": "2.6.2",
"typescript": "^4.6.3"
}
}
}

View File

@ -8,7 +8,7 @@
"name": "aqua-ls-server",
"version": "0.0.1",
"dependencies": {
"@fluencelabs/aqua-language-server-api": "0.13.1",
"@fluencelabs/aqua-language-server-api": "0.13.2",
"global-dirs": "^3.0.0",
"vscode-languageserver": "^7.0.0",
"vscode-languageserver-textdocument": "^1.0.4",
@ -31,9 +31,9 @@
"license": "Apache-2.0"
},
"node_modules/@fluencelabs/aqua-language-server-api": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@fluencelabs/aqua-language-server-api/-/aqua-language-server-api-0.13.1.tgz",
"integrity": "sha512-RipKk2NZiwX72eEN5gfDbdzljsGGMva01sCukrdrxV+zdyh+9TVZCjlpaVwZsjW/5e1wRCk0DE+RbVTlDc3Ktg=="
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/@fluencelabs/aqua-language-server-api/-/aqua-language-server-api-0.13.2.tgz",
"integrity": "sha512-hvq4/pB4kPx9t8z9NwM0BYxcXDtyN6h+SS4dmmZ/wSSLIEVWglKDc8R/JXAky96sPwdr1zNrNpNTnH0Ta9xJpw=="
},
"node_modules/global-dirs": {
"version": "3.0.0",
@ -103,9 +103,9 @@
},
"dependencies": {
"@fluencelabs/aqua-language-server-api": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@fluencelabs/aqua-language-server-api/-/aqua-language-server-api-0.13.1.tgz",
"integrity": "sha512-RipKk2NZiwX72eEN5gfDbdzljsGGMva01sCukrdrxV+zdyh+9TVZCjlpaVwZsjW/5e1wRCk0DE+RbVTlDc3Ktg=="
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/@fluencelabs/aqua-language-server-api/-/aqua-language-server-api-0.13.2.tgz",
"integrity": "sha512-hvq4/pB4kPx9t8z9NwM0BYxcXDtyN6h+SS4dmmZ/wSSLIEVWglKDc8R/JXAky96sPwdr1zNrNpNTnH0Ta9xJpw=="
},
"global-dirs": {
"version": "3.0.0",

View File

@ -11,7 +11,7 @@
"url": "https://github.com/fluencelabs/aqua"
},
"dependencies": {
"@fluencelabs/aqua-language-server-api": "0.13.1",
"@fluencelabs/aqua-language-server-api": "0.13.2",
"global-dirs": "^3.0.0",
"vscode-languageserver": "^7.0.0",
"vscode-languageserver-textdocument": "^1.0.4",
@ -20,4 +20,4 @@
"scripts": {
"get-root": "npm root --global"
}
}
}

View File

@ -1,6 +1,9 @@
import { exec } from 'child_process';
import { dirname } from 'path';
import type { Imports } from './imports';
import { normalizeImports } from './imports';
export class FluenceCli {
readonly cliPath: string;
@ -16,13 +19,17 @@ export class FluenceCli {
* Returns output of `fluence aqua imports`
* in dir of @param filePath.
*/
async imports(filePath: string): Promise<string[]> {
async imports(filePath: string): Promise<Imports> {
const cwd = dirname(filePath);
const result = await this.runJson(['aqua', 'imports'], cwd);
if (Array.isArray(result) && result.every((i) => typeof i === 'string')) {
return result;
} else {
throw new Error(`Invalid result: ${JSON.stringify(result)}`);
try {
return normalizeImports(result);
} catch (e) {
if (e instanceof Error) {
throw new Error(`Error converting imports from fluence: ${e.message}`);
} else {
throw e;
}
}
}

61
server/src/imports.ts Normal file
View File

@ -0,0 +1,61 @@
export type Imports = Record<string, Record<string, string[]>>;
// Normalize imports to the new format
// Legacy format of an array of paths is converted to the new format
// Empty imports are converted to an empty object
export function normalizeImports(imports: unknown): Imports {
console.log('normalizeImports: ', imports);
// Empty imports
if (imports === undefined || imports === null) {
return {};
}
// Legacy imports - array of paths
if (Array.isArray(imports) && imports.every((i) => typeof i === 'string')) {
return {
'/': {
'': imports as string[],
},
};
}
const isStringArray = (v: unknown): v is string[] => {
return Array.isArray(v) && v.every((i) => typeof i === 'string');
};
// New imports - object of objects of paths or arrays of paths
// Inner single paths are normalized to arrays
if (typeof imports === 'object') {
for (const info of Object.values(imports)) {
if (typeof info !== 'object') {
throw new Error(`Invalid imports: ${JSON.stringify(imports)}`);
}
for (const [importPrefix, locations] of Object.entries(info)) {
if (typeof locations === 'string') {
info[importPrefix] = [locations];
} else if (!isStringArray(locations)) {
throw new Error(`Invalid imports: ${JSON.stringify(imports)}`);
}
}
}
return imports as Imports;
}
throw new Error(`Invalid imports: ${JSON.stringify(imports)}`);
}
// Deep merge two import settings, overriding the first with the second
export function uniteImports(pre: Imports, post: Imports): Imports {
const result: Imports = { ...pre };
for (const [importPrefix, locations] of Object.entries(post)) {
if (importPrefix in result) {
result[importPrefix] = { ...result[importPrefix], ...locations };
} else {
result[importPrefix] = locations;
}
}
return result;
}

View File

@ -1,10 +1,10 @@
import type { Configuration } from 'vscode-languageserver/lib/common/configuration';
import { URI } from 'vscode-uri';
import type { Imports } from './imports';
import { normalizeImports, uniteImports } from './imports';
import type { FluenceCli } from './cli';
type Imports = string[];
export interface Settings {
imports: Imports;
enableLegacyAutoImportSearch: boolean;
@ -17,7 +17,7 @@ class DocumentInfo {
/* Settings from configuration (or default) */
private settings: Settings;
/* Additional imports from CLI */
private imports: Imports = [];
private imports: Imports = {};
private importsLastUpdated = 0;
private importsUpdateRequested = true;
@ -28,7 +28,8 @@ class DocumentInfo {
getSettings(): Settings {
return {
...this.settings,
imports: [...this.settings.imports, ...this.imports],
// Imports from settings override imports from CLI
imports: uniteImports(this.imports, this.settings.imports),
};
}
@ -43,7 +44,7 @@ class DocumentInfo {
return isUpdateReady && this.importsUpdateRequested;
}
updateImports(imports: string[]) {
updateImports(imports: Imports) {
this.imports = imports;
this.importsLastUpdated = Date.now();
this.importsUpdateRequested = false;
@ -61,7 +62,7 @@ export interface SettingsManagerConfig {
*/
export class SettingsManager {
private readonly defaultSettings: Settings = {
imports: [],
imports: {},
enableLegacyAutoImportSearch: false,
};
private documents: Map<string, DocumentInfo> = new Map();
@ -149,6 +150,15 @@ export class SettingsManager {
section: 'aquaSettings',
});
if (settings) {
try {
settings.imports = normalizeImports(settings.imports);
} catch (e) {
// TODO: Maybe show some notification to user?
// Don't know how to handle it better.
console.error('Cannot normalize imports from settings: ', e);
settings.imports = {};
}
return settings;
}
}