diff --git a/packages/concerto-core/lib/basemodelmanager.js b/packages/concerto-core/lib/basemodelmanager.js index 36249638d..2786b261a 100644 --- a/packages/concerto-core/lib/basemodelmanager.js +++ b/packages/concerto-core/lib/basemodelmanager.js @@ -741,10 +741,59 @@ class BaseModelManager { */ filter(predicate){ const modelManager = new BaseModelManager({...this.options}, this.processFile); - const filteredModels = Object.values(this.modelFiles) - .map((modelFile) => modelFile.filter(predicate, modelManager)) + const removedFqns = []; // the list of FQN of types that have been removed + + // remove the types from model files, populating removedFqns + let filteredModels = Object.values(this.modelFiles) + .map((modelFile) => modelFile.filter(predicate, modelManager, removedFqns)) .filter(Boolean); - modelManager.addModelFiles(filteredModels); + + // remove concerto model files - as these are automatically added + // when we recreate the model manager below + filteredModels = filteredModels.filter(mf => !mf.isSystemModelFile()); + + // now update filteredModels to remove any imports of removed types + const modelsWithValidImports = filteredModels.map( modelFile => { + const ast = modelFile.getAst(); + let modified = false; + removedFqns.forEach( removedFqn => { + const ns = ModelUtil.getNamespace(removedFqn); + const isSystemImport = ns.startsWith('concerto@') || ns === 'concerto'; + if(!isSystemImport && modelFile.getImports().includes(removedFqn)) { + const removeName = ModelUtil.getShortName(removedFqn); + const removeNamespace = ModelUtil.getNamespace(removedFqn); + ast.imports = ast.imports.filter(imp => { + const remove = ModelUtil.getShortName(imp.$class) === 'ImportType' && + imp.name === removeName && + imp.namespace === removeNamespace; + if(remove) { + modified = true; + } + return !remove; + }); + ast.imports.forEach( imp => { + if(imp.namespace === removeNamespace) { + if(ModelUtil.getShortName(imp.$class) === 'ImportTypes') { + imp.types = imp.types.filter((type) => { + const remove = (type === removeName); + if(remove) { + modified = true; + } + return !remove; + }); + } + } + }); + } + }); + if(modified) { + return new ModelFile(this, ast, undefined, modelFile.fileName); + } + else { + return modelFile; + } + }); + modelManager.addModelFiles(modelsWithValidImports); return modelManager; } } diff --git a/packages/concerto-core/lib/introspect/modelfile.js b/packages/concerto-core/lib/introspect/modelfile.js index 85f51ba25..497e68cf6 100644 --- a/packages/concerto-core/lib/introspect/modelfile.js +++ b/packages/concerto-core/lib/introspect/modelfile.js @@ -200,7 +200,8 @@ class ModelFile extends Decorated { /** * Returns the types that have been imported into this ModelFile. * - * @return {string[]} The array of imports for this ModelFile + * @return {string[]} The array of fully-qualified names for types imported by + * this ModelFile */ getImports() { let result = []; @@ -830,11 +831,22 @@ class ModelFile extends Decorated { * * @param {FilterFunction} predicate - the filter function over a Declaration object * @param {ModelManager} modelManager - the target ModelManager for the filtered ModelFile + * @param {string[]} removedDeclarations - an array that will be populated with the FQN of removed declarations * @returns {ModelFile?} - the filtered ModelFile * @private */ - filter(predicate, modelManager){ - const declarations = this.declarations?.filter(predicate).map(declaration => declaration.ast); + filter(predicate, modelManager, removedDeclarations){ + let declarations = []; // ast for all included declarations + this.declarations?.forEach( declaration => { + const included = predicate(declaration); + if(!included) { + removedDeclarations.push(declaration.getFullyQualifiedName()); + } + else { + declarations.push(declaration.ast); + } + } ); + const ast = { ...this.ast, declarations: declarations, diff --git a/packages/concerto-core/test/modelmanager.js b/packages/concerto-core/test/modelmanager.js index d3b287843..9a982bc2c 100644 --- a/packages/concerto-core/test/modelmanager.js +++ b/packages/concerto-core/test/modelmanager.js @@ -1072,5 +1072,34 @@ concept Bar { filtered.validateModelFiles(); }); + + it('should remove imports for filtered types', () => { + modelManager.addCTOModel(`namespace child@1.0.0 + concept Used {} + concept Unused {} + `, 'child.cto', true); + + modelManager.addCTOModel(`namespace cousin@1.0.0 + concept AlsoUsed {} + `, 'cousin.cto', true); + + modelManager.addCTOModel(`namespace orphan@1.0.0 + concept Orphan {} + `, 'orphan.cto', true); + + modelManager.addCTOModel(`namespace test@1.0.0 + import child@1.0.0.Unused + import child@1.0.0.Used + import cousin@1.0.0.AlsoUsed + import child@1.0.0.{Used,Unused} + concept Person { + o Used used + o AlsoUsed alsoUsed + } + `, 'test.cto'); + const filtered = modelManager.filter(declaration => + ['concerto@1.0.0.Concept','test@1.0.0.Person','child@1.0.0.Used', 'cousin@1.0.0.AlsoUsed'].includes(declaration.getFullyQualifiedName())); + filtered.validateModelFiles(); + }); }); });