Merge pull request #4 from eMoflon/bugfix/import-scope-filtering

Bugfix/import scope filtering
This commit is contained in:
Janik 2024-01-10 13:06:11 +01:00 committed by GitHub
commit 6758e16cc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 191 additions and 117 deletions

View file

@ -30,18 +30,29 @@ export class ModelModelingLanguageCodeActionProvider implements CodeActionProvid
getCodeActions(document: LangiumDocument, params: CodeActionParams): MaybePromise<Array<Command | CodeAction>> {
const result: CodeAction[] = [];
const acceptor = (ca: CodeAction | undefined) => ca && result.push(ca);
const acceptor = (ca: CodeAction | CodeAction[] | undefined) => {
if (Array.isArray(ca)) {
result.push(...ca);
} else {
result.push(ca as CodeAction)
}
return ca;
};
for (const diagnostic of params.context.diagnostics) {
this.createCodeActions(diagnostic, document, acceptor);
}
return result;
}
private createCodeActions(diagnostic: Diagnostic, document: LangiumDocument, accept: (ca: CodeAction | undefined) => void): void {
private createCodeActions(diagnostic: Diagnostic, document: LangiumDocument, accept: (ca: CodeAction | CodeAction[] | undefined) => void): void {
switch (diagnostic.code) {
case IssueCodes.ImportAlreadyExists:
accept(this.fixDuplicateImport(diagnostic, document));
break;
case IssueCodes.ImportIsMissing:
accept(this.fixMissingImport(diagnostic, document));
break;
case IssueCodes.OppositeAnnotationMissing:
accept(this.fixMissingOppositeAnnotation(diagnostic, document));
break;
@ -239,6 +250,27 @@ export class ModelModelingLanguageCodeActionProvider implements CodeActionProvid
return undefined;
}
private fixMissingImport(diagnostic: Diagnostic, document: LangiumDocument): CodeAction[] | undefined {
const possibleImports: string[] = diagnostic.data as string[];
if (possibleImports.length == 0) {
return undefined;
}
return possibleImports.map(pimport => {
return ({
title: `Import ${pimport}`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: {start: {character: 0, line: 0}, end: {character: 0, line: 0}},
newText: `import "${pimport}";\n`
}]
}
}
} as CodeAction)
})
}
}

View file

@ -4,11 +4,14 @@ import {
DefaultScopeProvider,
EMPTY_SCOPE,
getContainerOfType,
getDocument,
LangiumDocument,
MapScope,
ReferenceInfo,
Scope,
stream,
Stream
Stream,
UriUtils
} from "langium";
import {
Attribute,
@ -20,6 +23,7 @@ import {
FunctionMacroCall,
FunctionVariable,
IMacro,
Import,
Interface,
isAttribute,
isClass,
@ -36,6 +40,7 @@ import {
isIFunction,
isIInstance,
isIMacro,
isImportAlias,
isInstanceLoop,
isInterface,
isMacroAssignStatement,
@ -309,8 +314,18 @@ export class ModelModelingLanguageScopeProvider extends DefaultScopeProvider {
}
return result;
}
} else if (isImportAlias(context.container)) {
if (context.property === "ref") {
const iprt: Import = context.container.$container;
const localDocumentUri: URI = getDocument(iprt).uri;
const importedDocURI: URI | undefined = ModelModelingLanguageUtils.resolveRelativeModelImport(iprt.target, localDocumentUri);
if (importedDocURI != undefined) {
return new MapScope(this.indexManager.allElements("Package", new Set([importedDocURI.toString()])));
}
return EMPTY_SCOPE;
}
}
console.log(`[GetScope] Return super scope [Container: ${context.container.$type} (${context.container.$cstNode?.range.start.line})]`);
//console.log(`[GetScope] Return super scope [Container: ${context.container.$type} (${context.container.$cstNode?.range.start.line})]`);
return super.getScope(context);
}
@ -325,32 +340,45 @@ export class ModelModelingLanguageScopeProvider extends DefaultScopeProvider {
}
const globalScope: Scope = super.getGlobalScope(referenceType, _context);
const localDocumentUri: URI = getDocument(modl).uri;
const localUriSet: Set<string> = new Set([localDocumentUri.toString()]);
const mappedRelativeUris: Map<string, URI | undefined> = new Map(modl.imports.filter(ip => ip.aliases.length == 0).map(ip => [ip.target, ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, localDocumentUri)]));
const mappedAliasedRelativeUris: Map<string, URI | undefined> = new Map(modl.imports.filter(ip => ip.aliases.length > 0).map(ip => [ip.target, ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, localDocumentUri)]));
const importedUris: URI[] = [...mappedRelativeUris.values()].filter(x => x != undefined).map(x => x as URI);
const importedAliasedUris: URI[] = [...mappedAliasedRelativeUris.values()].filter(x => x != undefined).map(x => x as URI);
const importedUriSet: Set<string> = new Set(importedUris.map(x => x.toString()));
const importedAliasedUriSet: Set<string> = new Set(importedAliasedUris.map(x => x.toString()));
const localDocScope: Scope = new MapScope(this.indexManager.allElements(referenceType, localUriSet));
const importedDocScope: Scope = new MapScope(this.indexManager.allElements(referenceType, importedUriSet));
const importedAliasedDocScope: Scope = new MapScope(this.indexManager.allElements(referenceType, importedAliasedUriSet));
const aliasDescriptions: AstNodeDescription[] = [];
modl.imports.forEach(ip => {
const importUri = URI.parse(ip.target);
ip.aliases.forEach(ipa => {
globalScope.getAllElements()
.filter(x => x.name == ipa.ref.$refText || x.name.startsWith(ipa.ref.$refText))
.filter(astNodeDesc => astNodeDesc.documentUri.path == importUri.path)
.forEach(targetAstNodeDescription => {
if (targetAstNodeDescription != undefined) {
const targetAstNode = this.getAstNodeByPath(targetAstNodeDescription);
if (targetAstNode != null) {
const updatedName = targetAstNodeDescription.name.replace(ipa.ref.$refText, ipa.alias);
aliasDescriptions.push(this.descriptions.createDescription(targetAstNode, updatedName));
} else {
console.warn(`[AliasResolution] TargetAstNode is null!`)
}
} else {
console.warn(`[AliasResolution] Could not resolve ${ipa.ref.$refText}!`)
}
});
})
})
return this.createScope(aliasDescriptions, globalScope);
const aliasMap: Map<string, Import> = new Map(modl.imports.filter(ip => ip.aliases.length > 0).map(ip => [ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, localDocumentUri)!.toString(), ip]));
importedAliasedDocScope.getAllElements().forEach(nodeDesc => {
const ip: Import | undefined = aliasMap.get(nodeDesc.documentUri.toString());
if (ip != undefined) {
ip.aliases.forEach(alias => {
if (nodeDesc.name == alias.ref.$refText || nodeDesc.name.startsWith(alias.ref.$refText)) {
const targetAstNode = this.getAstNodeByPath(nodeDesc);
if (targetAstNode != null) {
const updatedName = nodeDesc.name.replace(alias.ref.$refText, alias.alias);
aliasDescriptions.push(this.descriptions.createDescription(targetAstNode, updatedName));
} else {
console.warn(`[AliasResolution] TargetAstNode is null!`)
}
}
});
} else {
console.warn(`[AliasResolution] Could not determine correct import for ${nodeDesc.documentUri.toString()}!`)
}
});
const importedScope: AstNodeDescription[] = [...aliasDescriptions, ...importedDocScope.getAllElements()];
return this.createScope(importedScope, localDocScope)
}
getAvailableFunctionVariablesSelectors(fvar: FunctionVariable): TypedVariable[] | undefined {
@ -539,4 +567,15 @@ export class ModelModelingLanguageScopeProvider extends DefaultScopeProvider {
}
return combinedResult;
}
public getScopeFixingUris(referenceType: string, referenceName: string, parentDir: URI, excludedUris: Set<string>): string[] {
const possibleDocs: Set<string> = new Set([...this.services.shared.workspace.LangiumDocuments.all.filter(x => !excludedUris.has(x.uri.toString())).map(x => x.uri.toString())]);
const importableUris: string[] = [];
for (const element of this.indexManager.allElements(referenceType, possibleDocs)) {
if (element.name == referenceName) {
importableUris.push(UriUtils.relative(parentDir, element.documentUri));
}
}
return importableUris;
}
}

View file

@ -1,4 +1,4 @@
import {AstNode} from "langium";
import {AstNode, URI, UriUtils} from "langium";
import {
AbstractElement,
ArithExpr,
@ -460,4 +460,16 @@ export class ModelModelingLanguageUtils {
}
return abstElements
}
public static resolveRelativeModelImport(path: string, currentUri: URI): URI | undefined {
if (path === undefined || path.length === 0) {
return undefined;
}
const dirUri = UriUtils.dirname(currentUri);
let grammarPath = path;
if (!grammarPath.endsWith('.mml')) {
grammarPath += '.mml';
}
return UriUtils.resolvePath(dirUri, grammarPath);
}
}

View file

@ -1,4 +1,4 @@
import {AstNode, getDocument, LangiumDocument, ValidationAcceptor, ValidationChecks} from 'langium';
import {AstNode, getDocument, LangiumDocument, URI, UriUtils, ValidationAcceptor, ValidationChecks} from 'langium';
import {
AbstractElement,
ArithExpr,
@ -44,7 +44,6 @@ import {
VariableType
} from './generated/ast.js';
import type {ModelModelingLanguageServices} from './model-modeling-language-module.js';
import {URI} from "vscode-uri";
import {ModelModelingLanguageUtils} from "./model-modeling-language-utils.js";
/**
@ -303,40 +302,34 @@ export class ModelModelingLanguageValidator {
ctnr = ctnr.$container;
}
const documentURI = ctnr.$document?.uri;
const documentURI = getDocument(cls).uri;
let importedDocuments = new Set((ctnr as Model).imports.map(imprt => imprt.target));
let importedDocuments: Set<string> = new Set((ctnr as Model).imports.map(imprt => ModelModelingLanguageUtils.resolveRelativeModelImport(imprt.target, documentURI)).filter(x => x != undefined).map(x => x!.toString()));
cls.extendedClasses.forEach(extCls => {
const extClassUri = extCls.$nodeDescription?.documentUri;
if (documentURI == undefined || extClassUri == undefined) {
console.error("Undefined class path!");
} else {
if (extClassUri.path != documentURI.path) {
if (!importedDocuments.has(extClassUri.path)) {
accept('error', `${extCls.ref?.name} with path ${extClassUri.path} is not imported'.`, {
node: cls,
property: 'extendedClasses',
code: IssueCodes.ImportIsMissing
})
}
if (extCls.ref == undefined) {
const importableRelativePaths: string[] = this.services.references.ScopeProvider.getScopeFixingUris("Class", extCls.$refText, UriUtils.dirname(documentURI), new Set<string>(importedDocuments).add(documentURI.toString()));
if (importableRelativePaths.length > 0) {
accept('error', `Create an import statement to include the referenced Definition!`, {
node: cls,
property: 'extendedClasses',
code: IssueCodes.ImportIsMissing,
data: importableRelativePaths
})
}
}
})
cls.implementedInterfaces.forEach(implIntrfc => {
const implIntrfcUri = implIntrfc.$nodeDescription?.documentUri;
if (documentURI == undefined || implIntrfcUri == undefined) {
console.error("Undefined class path!");
} else {
if (implIntrfcUri.path != documentURI.path) {
if (!importedDocuments.has(implIntrfcUri.path)) {
accept('error', `${implIntrfc.ref?.name} with path ${implIntrfcUri.path} is not imported'.`, {
node: cls,
property: 'implementedInterfaces',
code: IssueCodes.ImportIsMissing
})
}
if (implIntrfc.ref == undefined) {
const importableRelativePaths: string[] = this.services.references.ScopeProvider.getScopeFixingUris("Interface", implIntrfc.$refText, UriUtils.dirname(documentURI), new Set<string>(importedDocuments).add(documentURI.toString()));
if (importableRelativePaths.length > 0) {
accept('error', `Create an import statement to include the referenced Definition!`, {
node: cls,
property: 'implementedInterfaces',
code: IssueCodes.ImportIsMissing,
data: importableRelativePaths
})
}
}
})
@ -348,23 +341,20 @@ export class ModelModelingLanguageValidator {
ctnr = ctnr.$container;
}
const documentURI = ctnr.$document?.uri;
const documentURI: URI = getDocument(intrfc).uri;
let importedDocuments = new Set((ctnr as Model).imports.map(imprt => imprt.target));
let importedDocuments: Set<string> = new Set((ctnr as Model).imports.map(imprt => ModelModelingLanguageUtils.resolveRelativeModelImport(imprt.target, documentURI)).filter(x => x != undefined).map(x => x!.toString()));
intrfc.extendedInterfaces.forEach(extIntrfc => {
const extIntrfcUri = extIntrfc.$nodeDescription?.documentUri;
if (documentURI == undefined || extIntrfcUri == undefined) {
console.error("Undefined interface path!");
} else {
if (extIntrfcUri.path != documentURI.path) {
if (!importedDocuments.has(extIntrfcUri.path)) {
accept('error', `${extIntrfc.ref?.name} with path ${extIntrfcUri.path} is not imported'.`, {
node: intrfc,
property: 'extendedInterfaces',
code: IssueCodes.ImportIsMissing
})
}
if (extIntrfc.ref == undefined) {
const importableRelativePaths: string[] = this.services.references.ScopeProvider.getScopeFixingUris("Class", extIntrfc.$refText, UriUtils.dirname(documentURI), new Set<string>(importedDocuments).add(documentURI.toString()));
if (importableRelativePaths.length > 0) {
accept('error', `Create an import statement to include the referenced Definition!`, {
node: intrfc,
property: 'extendedInterfaces',
code: IssueCodes.ImportIsMissing,
data: importableRelativePaths
})
}
}
})
@ -376,38 +366,32 @@ export class ModelModelingLanguageValidator {
ctnr = ctnr.$container;
}
const documentURI = (ctnr as Model).$document?.uri;
const documentURI: URI = getDocument(ref).uri;
let importedDocuments = new Set((ctnr as Model).imports.map(imprt => imprt.target));
let importedDocuments: Set<string> = new Set((ctnr as Model).imports.map(imprt => ModelModelingLanguageUtils.resolveRelativeModelImport(imprt.target, documentURI)).filter(x => x != undefined).map(x => x!.toString()));
const refTypeUri = ref.type.$nodeDescription?.documentUri;
if (documentURI == undefined || refTypeUri == undefined) {
console.error("Undefined interface path!");
} else {
if (refTypeUri.path != documentURI.path) {
if (!importedDocuments.has(refTypeUri.path)) {
accept('error', `${ref.type.ref?.name} with path ${refTypeUri.path} is not imported'.`, {
node: ref,
property: 'type',
code: IssueCodes.ImportIsMissing
})
}
if (ref.type.ref == undefined) {
const importableRelativePaths: string[] = this.services.references.ScopeProvider.getScopeFixingUris("Class", ref.type.$refText, UriUtils.dirname(documentURI), new Set<string>(importedDocuments).add(documentURI.toString()));
if (importableRelativePaths.length > 0) {
accept('error', `Create an import statement to include the referenced Definition!`, {
node: ref,
property: 'type',
code: IssueCodes.ImportIsMissing,
data: importableRelativePaths
})
}
}
if (ref.opposite != undefined) {
const oppositeTypeUri = ref.opposite.reference.$nodeDescription?.documentUri;
if (documentURI == undefined || oppositeTypeUri == undefined) {
console.error("Undefined interface path!");
} else {
if (oppositeTypeUri.path != documentURI.path) {
if (!importedDocuments.has(oppositeTypeUri.path)) {
accept('error', `${ref.opposite.reference.ref?.name} with path ${oppositeTypeUri.path} is not imported'.`, {
node: ref.opposite,
property: 'reference',
code: IssueCodes.ImportIsMissing
})
}
if (ref.opposite.reference.ref == undefined) {
const importableRelativePaths: string[] = this.services.references.ScopeProvider.getScopeFixingUris("CReference", ref.opposite.reference.$refText, UriUtils.dirname(documentURI), new Set<string>(importedDocuments).add(documentURI.toString()));
if (importableRelativePaths.length > 0) {
accept('error', `Create an import statement to include the referenced Definition!`, {
node: ref.opposite,
property: 'reference',
code: IssueCodes.ImportIsMissing,
data: importableRelativePaths
})
}
}
}
@ -628,18 +612,27 @@ export class ModelModelingLanguageValidator {
checkPackageShadowing(modl: Model, accept: ValidationAcceptor) {
const shadowedPackageNames: Set<string> = new Set(modl.packages.map(p => p.name));
const documentUri: URI = getDocument(modl).uri;
modl.imports.forEach(ip => {
const importedDocURI: URI = URI.parse(ip.target);
const importedDocURI: URI | undefined = ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, documentUri);
const docShadowedPackageNames: Set<string> = new Set();
if (this.services.shared.workspace.LangiumDocuments.hasDocument(importedDocURI)) {
const unshadowedPackageNames: Set<string> = new Set();
if (importedDocURI != undefined && this.services.shared.workspace.LangiumDocuments.hasDocument(importedDocURI)) {
const importedDocument: LangiumDocument = this.services.shared.workspace.LangiumDocuments.getOrCreateDocument(importedDocURI);
const importedRoot: Model = importedDocument.parseResult.value as Model;
importedRoot.packages.forEach(pk => {
if (shadowedPackageNames.has(pk.name)) {
docShadowedPackageNames.add(pk.name);
}
shadowedPackageNames.add(pk.name);
unshadowedPackageNames.add(pk.name);
});
ip.aliases.forEach(alias => {
if (alias.ref.ref != undefined) {
docShadowedPackageNames.delete(alias.ref.ref.name);
unshadowedPackageNames.delete(alias.ref.ref.name);
}
})
unshadowedPackageNames.forEach(x => shadowedPackageNames.add(x));
if (docShadowedPackageNames.size > 0) {
accept('error', `Imported document shadows the following package names: [${[...docShadowedPackageNames].join(', ')}]`, {
node: ip,
@ -658,8 +651,9 @@ export class ModelModelingLanguageValidator {
}
checkSelfImport(ip: Import, accept: ValidationAcceptor) {
const targetPath = ip.target;
if (targetPath == getDocument(ip).uri.path) {
const documentUri: URI = getDocument(ip).uri;
const importedDocURI: URI | undefined = ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, documentUri);
if (UriUtils.equals(documentUri, importedDocURI)) {
accept('error', `Document imports itself!`, {
node: ip,
property: 'target',
@ -669,23 +663,20 @@ export class ModelModelingLanguageValidator {
}
checkImportAliasRefsContained(ip: Import, accept: ValidationAcceptor) {
const importedDocURI: URI = URI.parse(ip.target);
ip.aliases.forEach((ipa, idx) => {
if (ipa.ref.$nodeDescription != undefined) {
if (ipa.ref.$nodeDescription.documentUri != undefined) {
if (ipa.ref.$nodeDescription.documentUri.path != importedDocURI.path) {
accept('error', `Package ${ipa.ref.$refText} is not defined in this document!`, {
node: ip,
property: 'aliases',
index: idx,
code: IssueCodes.AliasReferencesUnknownPackage
})
}
} else {
console.error("[AliasRefsCheck] NodeDescription is undefined!");
const documentUri: URI = getDocument(ip).uri;
const importedDocURI: URI | undefined = ModelModelingLanguageUtils.resolveRelativeModelImport(ip.target, documentUri);
if (importedDocURI != undefined) {
ip.aliases.forEach((ipa, idx) => {
if (ipa.ref.ref == undefined || (!UriUtils.equals(getDocument(ipa.ref.ref).uri, importedDocURI))) {
accept('error', `Package ${ipa.ref.$refText} is not defined in this document!`, {
node: ip,
property: 'aliases',
index: idx,
code: IssueCodes.AliasReferencesUnknownPackage
})
}
}
});
});
}
}
checkMacroAttributeStatementType(mas: MacroAttributeStatement, accept: ValidationAcceptor) {