Skip to content

Commit 191bd4d

Browse files
authored
Merge pull request #9 from admvx/v1.1.0
v1.1.0
2 parents 4b86e8c + 056a803 commit 191bd4d

File tree

8 files changed

+114
-103
lines changed

8 files changed

+114
-103
lines changed

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@ https://github.com/admvx/as2-language-support
77
- LSP-based code completion
88
- Intrinsic language features + local code parsing
99

10-
## Known issues
11-
- Look-up of imported classes requires the open vscode directory to match the class root
12-
- Method signature intellisense supports single-line cases only
13-
- Limited awareness of inline method definitions
14-
1510
## Coming soon
16-
- File handling improvements
1711
- Go-to / peek definition support
1812
- Contextual suggestion filtering
1913
- Performance improvements via WASM regex implementation
2014

2115
## Release Notes
2216

17+
### 1.1.0
18+
- Removes the requirement that the active vscode workspace directory must match the root class-path of any open files
19+
- Improves handling of wildcard imports
20+
- Fixes bug where parse queue halts upon encountering a missing file
21+
2322
### 1.0.1
2423
Addresses the following issues:
2524
- No method signature support for the super constructor

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "git",
77
"url": "git@github.com:admvx/as2-language-support.git"
88
},
9-
"version": "1.0.1",
9+
"version": "1.1.0",
1010
"author": "Adam Vernon",
1111
"publisher": "admvx",
1212
"icon": "icon.png",

server/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"fs-extra": "8.1.0",
1616
"monaco-textmate": "^3.0.1",
1717
"onigasm": "^2.2.4",
18-
"vscode-languageserver": "5.3.0-next.10"
18+
"vscode-languageserver": "5.3.0-next.10",
19+
"vscode-uri": "^2.1.1"
1920
}
2021
}

server/src/action-context.ts

Lines changed: 29 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@ const stringMatcher = /(?:".*?"|'.*?'|["'].*?$)/g; //Matches st
1414
const braceMatcher = /(?:{(?:(?!{).)*?}|{(?:(?!{).)*?$)/g; //Matches well paired braces (complete or open to end of string)
1515
const bracketMatcher = /(?:\[(?:(?!\[).)*?\]|\[(?:(?!\[).)*?$)/g; //Matches well paired square brackets (complete or open to end of string)
1616
const matchedParens = /(?:\((?:(?!\().)*?\))/g; //Matches well paired parentheses
17-
const slashMatcher = /\//g; //Matches forward slashes
18-
const asExtensionMatcher = /\.as$/i; //Matches strings ending in .as
1917

2018
interface SymbolChainLink { identifier: string; called: boolean; }
2119

2220
export class ActionContext {
2321

24-
public static rootPath: string = ''; //TODO: Need a more accurate value for this to avoid i/o errors when the vscode folder is not the class root
25-
2622
private static _classes: ActionClass[] = [];
23+
private static _wildcardImports: { [path: string]: Promise<string[]> } = Object.create(null);
2724
private static _classLookup: { [fullTypeOrUri: string]: ActionClass } = Object.create(null);
2825
private static _intrinsicImports: { [shortType: string]: ActionClass } = Object.create(null);
2926
private static _globalCompletions: CompletionItem[] = [];
@@ -85,7 +82,7 @@ export class ActionContext {
8582
}
8683
}
8784
returnType = nextClass.importMap[returnType] || returnType;
88-
nextClass = await this.getClassByFullType(returnType);
85+
nextClass = await this.getClassByFullType(returnType, nextClass.baseUri);
8986
if (symbolCalled && nextClass.constructorMethod) {
9087
nextVisibility = VisibilityFilter.PUBLIC_INSTANCE;
9188
}
@@ -124,7 +121,7 @@ export class ActionContext {
124121

125122
let member: ActionParameter;
126123
if (ambientClass.superClass && chainLength === 1 && symbolChain[0].identifier === 'super') {
127-
let superClass = await this.getClassByFullType(ambientClass.superClass);
124+
let superClass = await this.getClassByFullType(ambientClass.superClass, ambientClass.baseUri);
128125
member = superClass && superClass.constructorMethod;
129126
} else {
130127
let memberAndClass = await this.traverseSymbolChainToMember(symbolChain, ambientClass, lineIndex);
@@ -247,7 +244,7 @@ export class ActionContext {
247244
symbolName = symbolChain[0].identifier;
248245
returnType = ambientClass.importMap[symbolName];
249246
if (returnType) {
250-
return [null, await this.getClassByFullType(returnType)];
247+
return [null, await this.getClassByFullType(returnType, ambientClass.baseUri)];
251248
} else if (this._intrinsicImports[symbolName]) {
252249
return [null, this._intrinsicImports[symbolName]];
253250
}
@@ -287,7 +284,7 @@ export class ActionContext {
287284
}
288285
}
289286
returnType = nextClass.importMap[returnType] || returnType;
290-
nextClass = await this.getClassByFullType(returnType);
287+
nextClass = await this.getClassByFullType(returnType, nextClass.baseUri);
291288
if (symbolCalled && nextClass.constructorMethod) {
292289
nextVisibility = VisibilityFilter.PUBLIC_INSTANCE;
293290
}
@@ -373,7 +370,7 @@ export class ActionContext {
373370
}
374371

375372
if (actionClass.superClass) {
376-
let superActionClass = await this.getClassByFullType(actionClass.superClass);
373+
let superActionClass = await this.getClassByFullType(actionClass.superClass, actionClass.baseUri);
377374
if (superActionClass) {
378375
completionItems = completionItems.concat(await this.getInnerSymbols(superActionClass, visibilityFilter, null, false, skipMap, rank));
379376
}
@@ -451,27 +448,18 @@ export class ActionContext {
451448
this._classLookup[actionClass.fileUri] = actionClass;
452449
}
453450

454-
public static getClassByFullType(fullType: string): Thenable<ActionClass> {
455-
if (! fullType) {
456-
return Promise.resolve(null);
457-
}
458-
if (this._classLookup[fullType]) {
459-
return Promise.resolve(this._classLookup[fullType]);
460-
}
461-
return this.requestClassParse(fullType);
462-
}
463-
464-
public static requestClassParse(fullType: string, forceReparse: boolean = false): Thenable<ActionClass> {
465-
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.INFO, message: `request class parse! ${fullType}` });
451+
public static getClassByFullType(fullType: string, basePath: string, forceReparse = false): Thenable<ActionClass> {
452+
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.VERBOSE, message: `Request class parse! ${fullType}` });
466453

467454
if (fullType in this._classLookup && !forceReparse) {
468455
return Promise.resolve(this._classLookup[fullType]);
469456
} else {
470-
let path = this.typeOrPackageToPath(fullType);
471-
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.INFO, message: `Type to path: ${path}` });
457+
let path = this.typeOrPackageToPath(fullType, basePath);
458+
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.VERBOSE, message: `Type to path: ${path}` });
472459
let promises: [PromiseLike<any>, Promise<string>] = [ActionParser.initialise(), LoadQueue.enqueue(path)];
473460
return Promise.all(promises)
474461
.then(res => {
462+
if (! res) return null;
475463
let ac = ActionParser.parseFile(path, res[1]);
476464
this.registerClass(ac);
477465
return ac;
@@ -480,18 +468,22 @@ export class ActionContext {
480468
}
481469
}
482470

483-
public static requestPackageParse(packageDescriptor: string, forceReparse: boolean = false): Promise<string[]> {
484-
return DirectoryUtility.fileNamesInDirectory(this.typeOrPackageToPath(packageDescriptor), /\.as$/i)
485-
.then(fileNames => fileNames.map(fileName => {
486-
let fullType = this.pathToFullType(fileName);
487-
if (fullType && (forceReparse || !this._classLookup[fullType])) {
488-
let path = this.typeOrPackageToPath(fullType);
489-
LoadQueue.enqueue(path)
490-
.then(contents => this.registerClass(ActionParser.parseFile(path, contents)))
491-
.catch(e => ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.ERROR, message: `Load error! ${path} ${e}` }));
492-
}
493-
return fullType;
494-
}));
471+
public static requestPackageParse(packageDescriptor: string, basePath: string, forceReparse = false): Promise<string[]> {
472+
let lookupKey = basePath + packageDescriptor;
473+
if (!forceReparse && lookupKey in this._wildcardImports) {
474+
return this._wildcardImports[lookupKey];
475+
}
476+
this._wildcardImports[lookupKey] = DirectoryUtility.fileNamesInDirectory(this.typeOrPackageToPath(packageDescriptor, basePath), /\.as$/i)
477+
.then(fileNames => Promise.all(fileNames.map(fileName => {
478+
return LoadQueue.enqueue(fileName)
479+
.then(contents => {
480+
if (! contents) return null;
481+
let parsedClass = ActionParser.parseFile(fileName, contents);
482+
this.registerClass(parsedClass);
483+
return parsedClass.fullType;
484+
});
485+
})));
486+
return this._wildcardImports[lookupKey];
495487
}
496488

497489
public static fullTypeToPackage(fullType: string): string {
@@ -506,26 +498,14 @@ export class ActionContext {
506498
return rawType.indexOf('.') === -1;
507499
}
508500

509-
public static typeOrPackageToPath(typeDescriptor: string): string {
501+
public static typeOrPackageToPath(typeDescriptor: string, basePath: string): string {
510502
let isPackage = this.fullTypeToShortType(typeDescriptor) === '*';
511-
let path = DirectoryUtility.concatPaths(this.rootPath, DirectoryUtility.typeToPath(typeDescriptor));
503+
let path = DirectoryUtility.concatPaths(basePath, DirectoryUtility.typeToPath(typeDescriptor));
512504
if (isPackage) {
513505
return path.substring(0, path.length-1);
514506
} else {
515507
return path + '.as';
516508
}
517509
}
518510

519-
public static pathToFullType(path: string): string {
520-
if (path.indexOf(this.rootPath) !== 0) {
521-
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.WARNING, message: `Can't determine type from path: '${path}' as it does not begin with the current rootPath: '${this.rootPath}'!` });
522-
return '';
523-
}
524-
if (path.substr(-3).toLowerCase() !== '.as') {
525-
ActionConfig.LOG_LEVEL !== LogLevel.NONE && logIt({ level: LogLevel.WARNING, message: `Unexpected file extension in path '${path}'!` });
526-
return '';
527-
}
528-
return path.substring(this.rootPath.length).replace(slashMatcher, '.').replace(asExtensionMatcher, '');
529-
}
530-
531511
}

server/src/action-elements.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { ActionContext } from './action-context';
22
import { logIt, LogLevel, ActionConfig } from './config';
33
import { SignatureInformation } from 'vscode-languageserver';
4+
import { DirectoryUtility } from './file-system-utilities';
45

56
export class ActionClass {
67

78
public shortType: string;
89
public fullType: string;
910
public fileUri: string;
11+
public baseUri: string;
1012
public isIntrinsic: boolean;
1113
public superClass: string;
1214
public parentPackage: string;
@@ -60,17 +62,23 @@ export class ActionClass {
6062
}
6163
}
6264

63-
public registerRegularImport(fullType: string, shortType?: string): void {
65+
public registerRegularImport(fullType: string, shortType?: string, skipParse?: boolean): void {
6466
shortType = shortType || ActionContext.fullTypeToShortType(fullType);
6567
this._imports.push(fullType);
6668
this._importMap[shortType] = fullType;
67-
ActionContext.requestClassParse(fullType);
69+
if (! skipParse) {
70+
ActionContext.getClassByFullType(fullType, this.baseUri, false);
71+
}
6872
}
6973

7074
public registerWildcardImport(wildcardImport: string): void {
7175
this._wildcardImports.push(wildcardImport);
72-
ActionContext.requestPackageParse(wildcardImport)
73-
.then(packageMembers => packageMembers.forEach(fullType => this.registerRegularImport(fullType)));
76+
ActionContext.requestPackageParse(wildcardImport, this.baseUri, false)
77+
.then(packageMembers => packageMembers.forEach(importType => {
78+
if (importType && importType !== this.fullType) {
79+
this.registerRegularImport(importType, null, true);
80+
}
81+
}));
7482
}
7583

7684
public getMemberByName(memberName: string, filter: VisibilityFilter = VisibilityFilter.PUBLIC_INSTANCE, lineNumber: number = null): PromiseLike<ActionParameter> {
@@ -103,7 +111,7 @@ export class ActionClass {
103111
if (lookup[memberName]) {
104112
return Promise.resolve(lookup[memberName]);
105113
} else if (this.superClass) {
106-
return ActionContext.getClassByFullType(this.superClass)
114+
return ActionContext.getClassByFullType(this.superClass, this.baseUri, false)
107115
.then(parsedSuperClass => parsedSuperClass && parsedSuperClass.getMemberByName(memberName, filter));
108116
}
109117
return null;
@@ -146,6 +154,7 @@ export class ActionClass {
146154
});
147155
}
148156
this._importMap[this.shortType] = this.fullType;
157+
this.baseUri = DirectoryUtility.fileUriAndTypeToBaseUri(this.fileUri, this.fullType);
149158
}
150159

151160
public get publicMembers(): ActionParameter[] { return this._publicInstanceMembers; }

server/src/action-parser.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export class ActionParser {
5656
this.wipClass.fileUri = fileUri;
5757
this.wipClass.lines = fileContent.split(/\r?\n/);
5858

59+
let imports: string[] = [];
60+
5961
let scopeStack: ActionScope[] = [ActionScope.BASE];
6062
for (let i = 0, l = this.wipClass.lines.length; i < l; i++) {
6163
line = this.wipClass.lines[i];
@@ -66,13 +68,7 @@ export class ActionParser {
6668
Patterns.IMPORT.lastIndex = 0;
6769
result = Patterns.IMPORT.exec(line);
6870
if (result) {
69-
fullType = result[1];
70-
shortType = ActionContext.fullTypeToShortType(fullType);
71-
if (shortType === '*') {
72-
this.wipClass.registerWildcardImport(fullType);
73-
} else {
74-
this.wipClass.registerRegularImport(fullType, shortType);
75-
}
71+
imports.push(result[1]);
7672
break;
7773
}
7874

@@ -145,19 +141,29 @@ export class ActionParser {
145141

146142
if (isConstructor) {
147143
this.wipClass.constructorMethod = method;
148-
}// else {
144+
}
149145
this.wipClass.registerMember(method);
150-
// }
151146

152147
i += methodLength;
153148
break;
154149
}
155150

156151
break;
157-
158152
}
159153
}
154+
160155
this.wipClass.setupSelfReferences();
156+
157+
//Register imports and parse external files
158+
imports.forEach(fullType => {
159+
shortType = ActionContext.fullTypeToShortType(fullType);
160+
if (shortType === '*') {
161+
this.wipClass.registerWildcardImport(fullType);
162+
} else {
163+
this.wipClass.registerRegularImport(fullType, shortType);
164+
}
165+
});
166+
161167
return this.wipClass;
162168
}
163169

server/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ setConsole(console);
2727
export function logIt(logConfig: { level: LogLevel, message: any } | any, ...rest: any[]) {
2828
if (ActionConfig.LOG_LEVEL === LogLevel.NONE) return;
2929

30-
let value: any = logConfig.level ? logConfig.message : Array.prototype.join.call(arguments, ' ');
31-
let level: LogLevel = logConfig.level || LogLevel.DEBUG;
30+
let value: any = (logConfig && logConfig.level) ? logConfig.message : Array.prototype.join.call(arguments, ' ');
31+
let level: LogLevel = (logConfig && logConfig.level) || LogLevel.DEBUG;
3232

3333
if (ActionConfig.LOG_LEVEL > level) return;
3434

0 commit comments

Comments
 (0)