From 19d9042f1d4cac91302dd2da191b1cde80cbbe4e Mon Sep 17 00:00:00 2001 From: mmews-n4 Date: Fri, 23 Aug 2024 14:56:33 +0200 Subject: [PATCH] GH-2638: Improve progress logging during initialisation and check performance (#2640) * integrate scan workspace into build progress * only respect direct dependencies to reduce computation * pass import declaration to infer importing project * keep checks in sync * if not found during validation, check workspace wide for project * clean-up * renamings * fixes * fix --- .../n4js/cli/compiler/N4jscCompiler.java | 11 +- .../cli/compiler/N4jscLanguageClient.java | 2 +- .../server/build/DtsAfterBuildListener.java | 2 +- .../build/N4JSBuildOrderInfoComputer.java | 19 +- .../server/commands/N4JSCommandService.java | 4 +- .../ModuleSpecifierTransformationDts.java | 3 +- .../CommonJsImportsTransformation.java | 14 +- .../ModuleSpecifierTransformation.java | 20 +- .../ide/server/LanguageServerFrontend.java | 18 +- .../xtext/ide/server/XLanguageServerImpl.java | 14 +- .../ide/server/build/BuilderFrontend.java | 11 +- .../ide/server/build/LspProgessUtil.java | 113 ++++++ .../ide/server/build/XWorkspaceBuilder.java | 337 ++++++++---------- .../ide/server/build/XWorkspaceManager.java | 17 +- .../n4js/packagejson/PackageJsonHelper.java | 14 +- .../projectDescription/ProjectReference.java | 2 +- .../AbstractTypeVisibilityChecker.java | 27 +- .../n4js/tooling/compare/ApiImplMapping.java | 4 +- .../tooling/compare/ProjectCompareHelper.java | 3 +- .../n4js/utils/N4JSLanguageHelper.java | 54 +-- ...4JSProjectSetupJsonValidatorExtension.java | 15 +- .../n4js/workspace/N4JSProjectConfig.java | 10 +- .../workspace/N4JSProjectConfigSnapshot.java | 18 +- .../N4JSWorkspaceConfigSnapshot.java | 25 ++ .../n4js/workspace/WorkspaceAccess.java | 2 +- .../utils/SemanticDependencySupplier.java | 8 +- .../IncrementalBuilderGenerateTest.java | 2 +- .../ide/tests/server/CommandRebuildTest.java | 2 +- 28 files changed, 455 insertions(+), 316 deletions(-) create mode 100644 plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/LspProgessUtil.java diff --git a/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscCompiler.java b/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscCompiler.java index b65869c5c6..390be59542 100644 --- a/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscCompiler.java +++ b/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscCompiler.java @@ -20,7 +20,6 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.eclipse.lsp4j.InitializeParams; -import org.eclipse.lsp4j.InitializedParams; import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.n4js.cli.N4jscConsole; import org.eclipse.n4js.cli.N4jscException; @@ -84,6 +83,11 @@ public N4jscExitState start() throws Exception { params.setWorkspaceFolders(Collections.singletonList(new WorkspaceFolder(baseDir.toURI().toString()))); languageServer.initialize(params).get(); + LanguageServerFrontend frontend = languageServer.getFrontend(); + frontend.initialized(languageServer.getBaseDir()); + N4jscConsole.println("Scanning workspace ..."); + workspaceManager.createWorkspaceConfig(); + throwIfNoProjectsFound(); verbosePrintAllProjects(); @@ -120,9 +124,10 @@ private void performCompile() { callback.resetCounters(); } Stopwatch compilationTime = Stopwatch.createStarted(); + LanguageServerFrontend frontend = languageServer.getFrontend(); try (Measurement m = N4JSDataCollectors.dcBuild.getMeasurement()) { - languageServer.initialized(new InitializedParams()); - languageServer.joinServerRequests(); + frontend.rebuildWorkspace(false); + frontend.join(); } printCompileResults(compilationTime.stop()); } diff --git a/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscLanguageClient.java b/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscLanguageClient.java index f1b3cb8a54..5d77179c80 100644 --- a/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscLanguageClient.java +++ b/plugins/org.eclipse.n4js.cli/src/org/eclipse/n4js/cli/compiler/N4jscLanguageClient.java @@ -120,7 +120,7 @@ public void notifyProgress(ProgressParams params) { } if (notification instanceof WorkDoneProgressReport) { WorkDoneProgressReport report = (WorkDoneProgressReport) notification; - String msg = String.format("%2d%% %dERRs %dWRNs %s", report.getPercentage(), errCount, wrnCount, + String msg = String.format(" %2d%% %dERRs %dWRNs %s", report.getPercentage(), errCount, wrnCount, report.getMessage()); N4jscConsole.setInfoLine(msg); } diff --git a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/DtsAfterBuildListener.java b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/DtsAfterBuildListener.java index 105e67b811..b34cd0a16c 100644 --- a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/DtsAfterBuildListener.java +++ b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/DtsAfterBuildListener.java @@ -90,7 +90,7 @@ private String createLibValue() { sb.append("["); sb.append("\"es2019\", \"es2020\""); for (ProjectReference requiredLibRef : projectConfig.getProjectDescription().getRequiredRuntimeLibraries()) { - N4JSPackageName requiredLibName = requiredLibRef.getN4JSProjectName(); + N4JSPackageName requiredLibName = requiredLibRef.getN4JSPackageName(); ImmutableSet dtsLibNames = N4JSGlobals.N4JS_DTS_LIB_CORRESPONDENCE.get(requiredLibName); for (String dtsLibName : dtsLibNames) { sb.append(", \""); diff --git a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/N4JSBuildOrderInfoComputer.java b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/N4JSBuildOrderInfoComputer.java index 88f22fa960..ed42db7650 100644 --- a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/N4JSBuildOrderInfoComputer.java +++ b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/build/N4JSBuildOrderInfoComputer.java @@ -12,6 +12,7 @@ import java.util.Set; +import org.eclipse.n4js.packagejson.projectDescription.ProjectDescription; import org.eclipse.n4js.packagejson.projectDescription.ProjectType; import org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot; import org.eclipse.n4js.xtext.workspace.BuildOrderFactory; @@ -30,12 +31,18 @@ protected Set getDependencies(ProjectConfigSnapshot pc) { N4JSProjectConfigSnapshot n4pcs = pc instanceof N4JSProjectConfigSnapshot ? (N4JSProjectConfigSnapshot) pc : null; - if (n4pcs != null && n4pcs.getType() == ProjectType.PLAINJS) { - // ignore dependencies of plain-JS projects to non-n4js-lib projects, because - // (1) they are irrelevant for the build order of N4JS code, - // (2) npm packages sometimes declare cyclic dependencies (and we must not show errors for those cycles) - Set n4jsDeps = Sets.filter(dependencies, dep -> n4pcs.isKnownDependency(dep)); - return n4jsDeps; + + if (n4pcs != null) { + ProjectDescription prjDescr = n4pcs.getProjectDescription(); + + // keep in sync with ProjectDiscoveryHelper#findDependencies() + if (!prjDescr.hasN4JSNature() && !prjDescr.isWorkspaceRoot() && prjDescr.getTypes() == null) { + // ignore dependencies of plain-JS projects to non-n4js-lib projects, because + // (1) they are irrelevant for the build order of N4JS code, + // (2) npm packages sometimes declare cyclic dependencies (and we must not show errors for those cycles) + Set n4jsDeps = Sets.filter(dependencies, dep -> n4pcs.isKnownDependency(dep)); + return n4jsDeps; + } } return dependencies; } diff --git a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/commands/N4JSCommandService.java b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/commands/N4JSCommandService.java index 7c493a8904..4e3f33602b 100644 --- a/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/commands/N4JSCommandService.java +++ b/plugins/org.eclipse.n4js.ide/src/org/eclipse/n4js/ide/server/commands/N4JSCommandService.java @@ -227,7 +227,7 @@ public Void refresh(ILanguageServerAccess access, CancelIndicator cancelIndicato @ExecutableCommandHandler(N4JS_REBUILD) public Void rebuild(ILanguageServerAccess access, CancelIndicator cancelIndicator) { builderFrontend.clean(); - builderFrontend.reinitWorkspace(); + builderFrontend.rebuildWorkspace(true); return null; } @@ -383,7 +383,7 @@ public Void installNpm( // @formatter:on return new CoarseGrainedChangeEvent(); }; - }).whenComplete((a, b) -> builderFrontend.reinitWorkspace()); + }).whenComplete((a, b) -> builderFrontend.rebuildWorkspace(true)); return null; } diff --git a/plugins/org.eclipse.n4js.transpiler.dts/src/org/eclipse/n4js/transpiler/dts/transform/ModuleSpecifierTransformationDts.java b/plugins/org.eclipse.n4js.transpiler.dts/src/org/eclipse/n4js/transpiler/dts/transform/ModuleSpecifierTransformationDts.java index 702402640a..c06550feee 100644 --- a/plugins/org.eclipse.n4js.transpiler.dts/src/org/eclipse/n4js/transpiler/dts/transform/ModuleSpecifierTransformationDts.java +++ b/plugins/org.eclipse.n4js.transpiler.dts/src/org/eclipse/n4js/transpiler/dts/transform/ModuleSpecifierTransformationDts.java @@ -10,6 +10,7 @@ */ package org.eclipse.n4js.transpiler.dts.transform; +import org.eclipse.n4js.n4JS.ImportDeclaration; import org.eclipse.n4js.transpiler.es.transform.ModuleSpecifierTransformation; import org.eclipse.n4js.ts.types.TModule; @@ -19,7 +20,7 @@ public class ModuleSpecifierTransformationDts extends ModuleSpecifierTransformation { @Override - protected String getActualFileExtension(TModule targetModule) { + protected String getActualFileExtension(ImportDeclaration importingDeclIM, TModule targetModule) { // file extensions are not required in module specifiers inside .d.ts files return null; } diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java index 2ddca09498..3e79a01563 100644 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/CommonJsImportsTransformation.java @@ -150,14 +150,18 @@ private List transformImportDecl(TModule targetModule, if (allImportDeclsForThisModule.isEmpty()) { return Collections.emptyList(); } - if (!requiresRewrite(targetModule)) { + ImportDeclaration importingDeclIM = allImportDeclsForThisModule.get(0); + if (!requiresRewrite(importingDeclIM, targetModule)) { return Collections.emptyList(); } List importDeclsToRewrite = new ArrayList<>(allImportDeclsForThisModule); if (exists(importDeclsToRewrite, id -> id.getModuleSpecifierForm() == ModuleSpecifierForm.PROJECT)) { + ImportDeclaration importingDeclOrigAST = (ImportDeclaration) getState().tracer + .getOriginalASTNode(importingDeclIM); + N4JSProjectConfigSnapshot targetProject = n4jsLanguageHelper.replaceDefinitionProjectByDefinedProject( - getState().resource, workspaceAccess.findProjectContaining(targetModule), true); + importingDeclOrigAST, workspaceAccess.findProjectContaining(targetModule), true); if (targetProject != null && targetProject.getProjectDescription().hasModuleProperty()) { // don't rewrite project imports in case the target project has a top-level property "module" in its // package.json, @@ -264,8 +268,10 @@ private void createVarDeclsOrBindings(List importDecls, Symbo } } - private boolean requiresRewrite(TModule targetModule) { - return !n4jsLanguageHelper.isES6Module(getState().index, targetModule); + private boolean requiresRewrite(ImportDeclaration importingDeclIM, TModule targetModule) { + ImportDeclaration importingDeclOrigAST = (ImportDeclaration) getState().tracer + .getOriginalASTNode(importingDeclIM); + return !n4jsLanguageHelper.isES6Module(getState().index, importingDeclOrigAST, targetModule); } private String computeNameForIntermediateDefaultImport(TModule targetModule) { diff --git a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java index b35a1d182b..9eb388c29a 100644 --- a/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java +++ b/plugins/org.eclipse.n4js.transpiler.es/src/org/eclipse/n4js/transpiler/es/transform/ModuleSpecifierTransformation.java @@ -85,8 +85,10 @@ public void transform() { } /** Returns file extension of output module */ - protected String getActualFileExtension(TModule targetModule) { - return n4jsLanguageHelper.getOutputFileExtension(getState().index, targetModule); + protected String getActualFileExtension(ImportDeclaration importingDeclIM, TModule targetModule) { + ImportDeclaration importingDeclOrigAST = (ImportDeclaration) getState().tracer + .getOriginalASTNode(importingDeclIM); + return n4jsLanguageHelper.getOutputFileExtension(getState().index, importingDeclOrigAST, targetModule); } private void transformImportDecl(ImportDeclaration importDeclIM) { @@ -160,7 +162,7 @@ private String computeModuleSpecifierForOutputCode(ImportDeclaration importDeclI // module specifiers are always absolute in N4JS, but Javascript requires relative module // specifiers when importing from a module within the same npm package // --> need to create a relative module specifier here: - return createRelativeModuleSpecifier(targetModule); + return createRelativeModuleSpecifier(importDeclIM, targetModule); } ModuleSpecifierForm moduleSpecifierForm = importDeclIM.getModuleSpecifierForm(); @@ -177,10 +179,10 @@ private String computeModuleSpecifierForOutputCode(ImportDeclaration importDeclI return importDeclIM.getModuleSpecifierAsText(); } - return createAbsoluteModuleSpecifier(targetProject, targetModule); + return createAbsoluteModuleSpecifier(targetProject, importDeclIM, targetModule); } - private String createRelativeModuleSpecifier(TModule targetModule) { + private String createRelativeModuleSpecifier(ImportDeclaration importingDeclIM, TModule targetModule) { String targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); String[] targetModuleSpecifierSegments = targetModuleSpecifier.split("/", -1); String targetModuleName = targetModuleSpecifierSegments[targetModuleSpecifierSegments.length - 1]; @@ -195,14 +197,16 @@ private String createRelativeModuleSpecifier(TModule targetModule) { List allSegm = new ArrayList<>(Arrays.asList(differingSegments)); allSegm.add(targetModuleName); int goUpCount = localModulePath.length - i; - String ext = getActualFileExtension(targetModule); + String ext = getActualFileExtension(importingDeclIM, targetModule); String result = ((goUpCount > 0) ? "../".repeat(goUpCount) : "./") + org.eclipse.n4js.utils.Strings.join("/", allSegm) + ((ext != null && !ext.isEmpty()) ? "." + ext : ""); return result; } - private String createAbsoluteModuleSpecifier(N4JSProjectConfigSnapshot targetProject, TModule targetModule) { + private String createAbsoluteModuleSpecifier(N4JSProjectConfigSnapshot targetProject, + ImportDeclaration importingDeclIM, TModule targetModule) { + if (N4JSLanguageUtils.isMainModule(targetProject, targetModule)) { // 'targetModule' is the main module of 'targetProject', so we can use a project import: return getActualProjectName(targetProject).toString(); @@ -236,7 +240,7 @@ private String createAbsoluteModuleSpecifier(N4JSProjectConfigSnapshot targetPro String targetModuleSpecifier = resourceNameComputer.getCompleteModuleSpecifier(targetModule); sb.append(targetModuleSpecifier); - String ext = getActualFileExtension(targetModule); + String ext = getActualFileExtension(importingDeclIM, targetModule); if (ext != null && !ext.isEmpty()) { sb.append('.'); sb.append(ext); diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/LanguageServerFrontend.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/LanguageServerFrontend.java index a6dbe7e423..665a3a5921 100644 --- a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/LanguageServerFrontend.java +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/LanguageServerFrontend.java @@ -114,11 +114,10 @@ public class LanguageServerFrontend implements TextDocumentService, WorkspaceSer /** * Initialize this front-end according to the given arguments. */ - public void initialize(InitializeParams params, URI baseDir, ILanguageServerAccess access) { + public void initialize(InitializeParams params, ILanguageServerAccess access) { logWelcomeMessage(); workspaceFrontend.initialize(access); textDocumentFrontend.initialize(params, access); - builderFrontend.initialize(baseDir); } /** @@ -131,8 +130,8 @@ protected void logWelcomeMessage() { /** * Notifies the front-end that the initialization was completed. */ - public void initialized() { - builderFrontend.initialBuild(); + public void initialized(URI baseDir) { + builderFrontend.initialize(baseDir); } /** @@ -191,7 +190,7 @@ public void didSave(DidSaveTextDocumentParams params) { @Override public void didChangeConfiguration(DidChangeConfigurationParams params) { - reinitWorkspace(); + rebuildWorkspace(); } @Override @@ -214,8 +213,13 @@ public void clean() { } /** Triggers rebuild of the whole workspace in the background. Can be awaited by {@link #join()}. */ - public void reinitWorkspace() { - builderFrontend.reinitWorkspace(); + public void rebuildWorkspace() { + rebuildWorkspace(true); + } + + /** Triggers rebuild of the whole workspace in the background. Can be awaited by {@link #join()}. */ + public void rebuildWorkspace(boolean recreateWorkspace) { + builderFrontend.rebuildWorkspace(recreateWorkspace); } @Override diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/XLanguageServerImpl.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/XLanguageServerImpl.java index 614ae670ca..6ae6549884 100644 --- a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/XLanguageServerImpl.java +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/XLanguageServerImpl.java @@ -266,16 +266,16 @@ public CompletableFuture initialize(InitializeParams params) { if (initializeParams != null) { throw new IllegalStateException("This language server has already been initialized."); } - URI baseDir = getBaseDir(params); if (languagesRegistry.getExtensionToFactoryMap().isEmpty()) { throw new IllegalStateException( "No Xtext languages have been registered. Please make sure you have added the languages\'s setup class in \'/META-INF/services/org.eclipse.xtext.ISetup\'"); } this.initializeParams = params; + URI baseDir = getBaseDir(); Stopwatch sw = Stopwatch.createStarted(); LOG.info("Start server initialization in workspace directory " + baseDir); - lsFrontend.initialize(initializeParams, baseDir, access); + lsFrontend.initialize(initializeParams, access); LOG.info("Server initialization done after " + sw); initializeResult = new InitializeResult(); @@ -392,7 +392,9 @@ protected Optional> getSupportedCodeActionKinds() { @Override public void initialized(InitializedParams params) { - lsFrontend.initialized(); + URI baseDir = getBaseDir(); + lsFrontend.initialized(baseDir); + lsFrontend.rebuildWorkspace(); } @Deprecated @@ -416,8 +418,8 @@ private URI deprecatedToBaseDir2(InitializeParams params) { /** * Compute the base directory. */ - protected URI getBaseDir(InitializeParams params) { - List workspaceFolders = params.getWorkspaceFolders(); + public URI getBaseDir() { + List workspaceFolders = initializeParams.getWorkspaceFolders(); if (workspaceFolders != null && !workspaceFolders.isEmpty()) { // TODO: Support multiple workspace folders WorkspaceFolder workspaceFolder = workspaceFolders.get(0); @@ -427,7 +429,7 @@ protected URI getBaseDir(InitializeParams params) { } return uriExtensions.toUri(workspaceFolder.getUri()); } - return deprecatedToBaseDir2(params); + return deprecatedToBaseDir2(initializeParams); } @SuppressWarnings("hiding") diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/BuilderFrontend.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/BuilderFrontend.java index 25ee6f7e28..1a3f67ee1a 100644 --- a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/BuilderFrontend.java +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/BuilderFrontend.java @@ -85,13 +85,6 @@ public void initialize(URI newBaseDir) { workspaceManager.initialize(newBaseDir); } - /** - * Trigger an initial build in the background. - */ - public void initialBuild() { - asyncRunBuildTask("initialBuild", workspaceBuilder::createInitialBuildTask); - } - /** * Trigger a clean operation in the background. Afterwards, all previously build artifacts have been removed. This * includes index data, source2generated mappings, cached issues and persisted index state. A subsequent build is @@ -104,8 +97,8 @@ public void clean() { /** * Triggers rebuild of the whole workspace */ - public void reinitWorkspace() { - asyncRunBuildTask("reinitWorkspace", workspaceBuilder::createReinitialBuildTask); + public void rebuildWorkspace(boolean recreateWorkspace) { + asyncRunBuildTask("rebuildWorkspace", () -> workspaceBuilder.createFullBuildTask(recreateWorkspace)); } /** diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/LspProgessUtil.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/LspProgessUtil.java new file mode 100644 index 0000000000..a29d455000 --- /dev/null +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/LspProgessUtil.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2024 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.xtext.ide.server.build; + +import org.eclipse.lsp4j.ProgressParams; +import org.eclipse.lsp4j.WorkDoneProgressBegin; +import org.eclipse.lsp4j.WorkDoneProgressCreateParams; +import org.eclipse.lsp4j.WorkDoneProgressEnd; +import org.eclipse.lsp4j.WorkDoneProgressNotification; +import org.eclipse.lsp4j.WorkDoneProgressReport; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.n4js.utils.TameAutoClosable; +import org.eclipse.n4js.xtext.ide.server.XLanguageServerImpl; + +import com.google.common.base.Stopwatch; +import com.google.inject.Inject; + +/** + * + */ +public class LspProgessUtil { + + @Inject + private XLanguageServerImpl languageServer; + + WorkDoneProgressCreateParams startProgress(WorkDoneProgressCreateParams currentProgress, String title, + String message) { + + if (currentProgress == null) { + Either token = Either.forRight((int) (System.currentTimeMillis() % Integer.MAX_VALUE)); + currentProgress = new WorkDoneProgressCreateParams(token); + languageServer.getLanguageClient().createProgress(currentProgress); + + WorkDoneProgressBegin progressNotification = new WorkDoneProgressBegin(); + progressNotification.setTitle(title); + progressNotification.setMessage(message); + progressNotification.setCancellable(false); + progressNotification.setPercentage(0); + Either notification = Either.forLeft(progressNotification); + ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); + languageServer.getLanguageClient().notifyProgress(progressParams); + } + return currentProgress; + } + + void updateProgress(WorkDoneProgressCreateParams currentProgress, String message, int percentage) { + if (currentProgress != null) { + WorkDoneProgressReport progressNotification = new WorkDoneProgressReport(); + progressNotification.setMessage(message); + progressNotification.setCancellable(false); + progressNotification.setPercentage(percentage); + Either notification = Either.forLeft(progressNotification); + ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); + languageServer.getLanguageClient().notifyProgress(progressParams); + } + } + + void endProgress(WorkDoneProgressCreateParams currentProgress, String message) { + if (currentProgress != null) { + WorkDoneProgressEnd progressNotification = new WorkDoneProgressEnd(); + progressNotification.setMessage(message); + Either notification = Either.forLeft(progressNotification); + ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); + languageServer.getLanguageClient().notifyProgress(progressParams); + } + } + + ProgressClosable start(String title, String message) { + WorkDoneProgressCreateParams progress = startProgress(null, title, message); + return new ProgressClosable(progress); + } + + class ProgressClosable implements TameAutoClosable { + final WorkDoneProgressCreateParams progress; + final Stopwatch stopwatch = Stopwatch.createUnstarted(); + String closeMsg; + + ProgressClosable(WorkDoneProgressCreateParams progress) { + this.progress = progress; + this.stopwatch.start(); + } + + public void update(String message, int percentage) { + updateProgress(progress, message, percentage); + } + + public void end(String msg) { + stopwatch.stop(); + closeMsg = msg; + } + + public void endWithTime(String msg) { + stopwatch.stop(); + closeMsg = msg + " (" + stopwatch.toString() + ")"; + } + + @Override + public void close() { + if (stopwatch.isRunning()) { + stopwatch.stop(); + } + endProgress(progress, closeMsg); + } + } +} diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceBuilder.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceBuilder.java index 63d5a32b77..1b5a1b387a 100644 --- a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceBuilder.java +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceBuilder.java @@ -21,17 +21,10 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.eclipse.emf.common.util.URI; -import org.eclipse.lsp4j.ProgressParams; -import org.eclipse.lsp4j.WorkDoneProgressBegin; -import org.eclipse.lsp4j.WorkDoneProgressCreateParams; -import org.eclipse.lsp4j.WorkDoneProgressEnd; -import org.eclipse.lsp4j.WorkDoneProgressNotification; -import org.eclipse.lsp4j.WorkDoneProgressReport; -import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.n4js.utils.UtilN4; import org.eclipse.n4js.xtext.ide.server.ResourceChangeSet; -import org.eclipse.n4js.xtext.ide.server.XLanguageServerImpl; import org.eclipse.n4js.xtext.ide.server.build.IBuildRequestFactory.OnPostCreateListener; +import org.eclipse.n4js.xtext.ide.server.build.LspProgessUtil.ProgressClosable; import org.eclipse.n4js.xtext.ide.server.build.ParallelBuildManager.ParallelJob; import org.eclipse.n4js.xtext.ide.server.build.XWorkspaceManager.UpdateResult; import org.eclipse.n4js.xtext.ide.server.util.LspLogger; @@ -91,7 +84,7 @@ public class XWorkspaceBuilder { private BuildOrderFactory buildOrderFactory; @Inject - private XLanguageServerImpl languageServer; + private LspProgessUtil lspProgess; private final Set newDirtyFiles = new LinkedHashSet<>(); private final Set newDeletedFiles = new LinkedHashSet<>(); @@ -104,29 +97,17 @@ public class XWorkspaceBuilder { /** Holds all deltas of all projects. In case of a cancelled build, this set is not empty at start of next build. */ private final List toBeConsideredDeltas = new ArrayList<>(); - private WorkDoneProgressCreateParams currentProgress; - /** - * Initializes the workspace and triggers an initial build (always non-cancelable). + * (Re-)Initializes the workspace and triggers the equivalent to an initial build (non-cancelable). */ - public BuildTask createInitialBuildTask() { - startProgress("Init build", "Loading..."); - return (cancelIndicator) -> this.doInitialBuild(); - } - - /** - * Re-initializes the workspace and triggers the equivalent to an initial build (also non-cancelable). - */ - public BuildTask createReinitialBuildTask() { - startProgress("Full build", "Re-initialize..."); + public BuildTask createFullBuildTask(boolean recreateWorkspace) { // because we are about to re-initialize the workspace and build everything anyway, we can get rid of all // changes reported up to this point (but do not clear #newDirty|DeletedFiles at then end of the initial build, // because changes that are reported during the initial build must not be overlooked!) newDirtyFiles.clear(); newDeletedFiles.clear(); - workspaceManager.reinitialize(); - return (cancelIndicator) -> this.doInitialBuild(); + return (cancelIndicator) -> this.doFullBuild(recreateWorkspace); } /** @@ -134,57 +115,65 @@ public BuildTask createReinitialBuildTask() { * * @return the delta. */ - private IResourceDescription.Event doInitialBuild() { - Stopwatch stopwatch = Stopwatch.createStarted(); + private IResourceDescription.Event doFullBuild(boolean recreateWorkspace) { + try (ProgressClosable progress = lspProgess.start("Build", "initialize...")) { - OnPostCreateListener postCreateListener = null; - WorkspaceConfigSnapshot workspaceConfig = workspaceManager.getWorkspaceConfig(); - try { - Collection allProjects = workspaceManager.getProjectConfigs(); - BuildOrderIterator pboIterator = buildOrderFactory.createBuildOrderIterator(workspaceConfig, allProjects); - logBuildOrder(pboIterator); + if (recreateWorkspace) { + progress.update("scanning workspace ...", 0); + workspaceManager.createWorkspaceConfig(); + } - postCreateListener = getPostCreateListener(workspaceConfig, allProjects); - buildRequestFactory.addOnPostCreateListener(postCreateListener); + progress.update("building ...", 0); - List allDeltas = new ArrayList<>(); - while (pboIterator.hasNext()) { - ProjectConfigSnapshot projectConfig = pboIterator.next(); - String projectID = projectConfig.getName(); - ProjectBuilder projectBuilder = workspaceManager.getProjectBuilder(projectID); + OnPostCreateListener postCreateListener = null; + WorkspaceConfigSnapshot workspaceConfig = workspaceManager.getWorkspaceConfig(); + try { + Collection allProjects = workspaceManager.getProjectConfigs(); + BuildOrderIterator pboIterator = buildOrderFactory.createBuildOrderIterator(workspaceConfig, + allProjects); + logBuildOrder(pboIterator); - XBuildResult partialresult = projectBuilder.doInitialBuild(buildRequestFactory, allDeltas); - allDeltas.addAll(partialresult.getAffectedResources()); - } + postCreateListener = getPostCreateListener(progress, workspaceConfig, allProjects); + buildRequestFactory.addOnPostCreateListener(postCreateListener); - onBuildDone(true, false, postCreateListener, Optional.absent()); + List allDeltas = new ArrayList<>(); + while (pboIterator.hasNext()) { + ProjectConfigSnapshot projectConfig = pboIterator.next(); + String projectID = projectConfig.getName(); + ProjectBuilder projectBuilder = workspaceManager.getProjectBuilder(projectID); - stopwatch.stop(); + XBuildResult partialresult = projectBuilder.doInitialBuild(buildRequestFactory, allDeltas); + allDeltas.addAll(partialresult.getAffectedResources()); + } - endProgress("Full build done. (" + stopwatch.toString() + ")"); - lspLogger.log("Full build done. (" + stopwatch.toString() + ")"); + onBuildDone(true, false, postCreateListener, Optional.absent()); - return new ResourceDescriptionChangeEvent(allDeltas); - } catch (Throwable th) { - boolean wasCanceled = operationCanceledManager.isOperationCanceledException(th); + progress.endWithTime("Full build done."); + lspLogger.log("Full build done. (" + progress.stopwatch.toString() + ")"); - onBuildDone(true, wasCanceled, postCreateListener, Optional.of(th)); + return new ResourceDescriptionChangeEvent(allDeltas); + } catch (Throwable th) { + boolean wasCanceled = operationCanceledManager.isOperationCanceledException(th); - if (wasCanceled) { - endProgress("Full build canceled."); - operationCanceledManager.propagateIfCancelException(th); - // returns here - } + onBuildDone(true, wasCanceled, postCreateListener, Optional.of(th)); - lspLogger.error("Full build ABORTED due to exception: ", th); - endProgress("Full build ABORTED due to an exception."); + if (wasCanceled) { + progress.endWithTime("Full build canceled."); + operationCanceledManager.propagateIfCancelException(th); + // returns here + } - throw th; + lspLogger.error("Full build ABORTED due to exception: ", th); + progress.end("full build ABORTED due to an exception."); + + throw th; + } } } @SafeVarargs - private OnPostCreateListener getPostCreateListener(WorkspaceConfigSnapshot workspaceConfig, + private OnPostCreateListener getPostCreateListener(ProgressClosable progress, + WorkspaceConfigSnapshot workspaceConfig, Collection allProjects, Map>... project2UriMaps) { OnPostCreateListener postCreateListener; @@ -210,7 +199,7 @@ private OnPostCreateListener getPostCreateListener(WorkspaceConfigSnapshot works todoList.remove(request.getProjectName(), uri); String msg = workspaceConfig.makeWorkspaceRelative(uri).toString(); int doneCnt = totalFileCount - todoList.size(); - updateProgress(msg, (100 * doneCnt) / totalFileCount); + progress.update(msg, (100 * doneCnt) / totalFileCount); }); request.addAfterBuildRequestListener((req, res) -> { todoList.removeAll(request.getProjectName()); @@ -219,48 +208,6 @@ private OnPostCreateListener getPostCreateListener(WorkspaceConfigSnapshot works return postCreateListener; } - private void startProgress(String title, String message) { - endProgress("Build terminated due to new build."); - - if (currentProgress == null) { - Either token = Either.forRight((int) (System.currentTimeMillis() % Integer.MAX_VALUE)); - currentProgress = new WorkDoneProgressCreateParams(token); - languageServer.getLanguageClient().createProgress(currentProgress); - - WorkDoneProgressBegin progressNotification = new WorkDoneProgressBegin(); - progressNotification.setTitle(title); - progressNotification.setMessage(message); - progressNotification.setCancellable(false); - progressNotification.setPercentage(0); - Either notification = Either.forLeft(progressNotification); - ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); - languageServer.getLanguageClient().notifyProgress(progressParams); - } - } - - private void updateProgress(String message, int percentage) { - if (currentProgress != null) { - WorkDoneProgressReport progressNotification = new WorkDoneProgressReport(); - progressNotification.setMessage(message); - progressNotification.setCancellable(false); - progressNotification.setPercentage(percentage); - Either notification = Either.forLeft(progressNotification); - ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); - languageServer.getLanguageClient().notifyProgress(progressParams); - } - } - - private void endProgress(String message) { - if (currentProgress != null) { - WorkDoneProgressEnd progressNotification = new WorkDoneProgressEnd(); - progressNotification.setMessage(message); - Either notification = Either.forLeft(progressNotification); - ProgressParams progressParams = new ProgressParams(currentProgress.getToken(), notification); - languageServer.getLanguageClient().notifyProgress(progressParams); - currentProgress = null; - } - } - /** * Run a full build on the workspace * @@ -360,100 +307,105 @@ public BuildTask createIncrementalBuildTask(List newDirtyFiles, List n * */ protected IResourceDescription.Event doIncrementalWorkspaceUpdateAndBuild(CancelIndicator cancelIndicator) { - startProgress("Build", ""); - - // in case many incremental build tasks pile up in the queue (e.g. while a non-cancelable initial build is - // running), we don't want to repeatedly invoke IWorkspaceManager#update() in each of those tasks but only in - // the last one; therefore, we here check for a cancellation: - operationCanceledManager.checkCanceled(cancelIndicator); - - Set newDirtyFiles = new LinkedHashSet<>(this.newDirtyFiles); - Set newDeletedFiles = new LinkedHashSet<>(this.newDeletedFiles); - boolean newRefreshRequest = this.newRefreshRequest; - this.newDirtyFiles.clear(); - this.newDeletedFiles.clear(); - this.newRefreshRequest = false; - - Stopwatch stopwatch = Stopwatch.createStarted(); - if (newRefreshRequest) { - updateProgress("Refreshing", 0); - } - - UpdateResult updateResult = workspaceManager.update(newDirtyFiles, newDeletedFiles, newRefreshRequest); - WorkspaceChanges changes = updateResult.changes; - - List actualDirtyFiles; - List actualDeletedFiles; - if (newRefreshRequest) { - // scan all source folders of all projects for source file additions, changes, and deletions - // - including source files of added projects, - // - including source files of added source folders of existing projects, - // - including source files of removed source folders of existing projects, - // - *not* including source files of removed projects. - actualDirtyFiles = new ArrayList<>(); - actualDeletedFiles = new ArrayList<>(); - for (ProjectBuilder projectBuilder : workspaceManager.getProjectBuilders()) { - ResourceChangeSet sourceFileChanges = projectBuilder.scanForSourceFileChanges(); - actualDirtyFiles.addAll(sourceFileChanges.getDirty()); - actualDeletedFiles.addAll(sourceFileChanges.getDeleted()); + try (ProgressClosable progress = lspProgess.start("Build", "")) { + + // in case many incremental build tasks pile up in the queue (e.g. while a non-cancelable initial build is + // running), we don't want to repeatedly invoke IWorkspaceManager#update() in each of those tasks but only + // in the last one; therefore, we here check for a cancellation: + operationCanceledManager.checkCanceled(cancelIndicator); + + Set newDirtyFiles = new LinkedHashSet<>(this.newDirtyFiles); + Set newDeletedFiles = new LinkedHashSet<>(this.newDeletedFiles); + boolean newRefreshRequest = this.newRefreshRequest; + this.newDirtyFiles.clear(); + this.newDeletedFiles.clear(); + this.newRefreshRequest = false; + + Stopwatch stopwatch = Stopwatch.createStarted(); + + String msg = "scanning%s workspace ...".formatted(newRefreshRequest ? " and refreshing" : ""); + progress.update(msg, 0); + + UpdateResult updateResult = workspaceManager.update(newDirtyFiles, newDeletedFiles, newRefreshRequest); + WorkspaceChanges changes = updateResult.changes; + + progress.update("building ...", 0); + + List actualDirtyFiles; + List actualDeletedFiles; + if (newRefreshRequest) { + // scan all source folders of all projects for source file additions, changes, and deletions + // - including source files of added projects, + // - including source files of added source folders of existing projects, + // - including source files of removed source folders of existing projects, + // - *not* including source files of removed projects. + actualDirtyFiles = new ArrayList<>(); + actualDeletedFiles = new ArrayList<>(); + for (ProjectBuilder projectBuilder : workspaceManager.getProjectBuilders()) { + ResourceChangeSet sourceFileChanges = projectBuilder.scanForSourceFileChanges(); + actualDirtyFiles.addAll(sourceFileChanges.getDirty()); + actualDeletedFiles.addAll(sourceFileChanges.getDeleted()); + } + } else { + actualDirtyFiles = UtilN4.concat(changes.getAddedURIs(), changes.getChangedURIs()); + // scan only the added source folders (including those of added projects) for source files + actualDirtyFiles.addAll(scanAddedSourceFoldersForNewSourceFiles(changes, scanner)); + + actualDeletedFiles = new ArrayList<>(changes.getRemovedURIs()); + // collect URIs from removed source folders (*not* including those of removed projects) + actualDeletedFiles.addAll(getURIsFromRemovedSourceFolders(changes)); } - } else { - actualDirtyFiles = UtilN4.concat(changes.getAddedURIs(), changes.getChangedURIs()); - // scan only the added source folders (including those of added projects) for source files - actualDirtyFiles.addAll(scanAddedSourceFoldersForNewSourceFiles(changes, scanner)); - actualDeletedFiles = new ArrayList<>(changes.getRemovedURIs()); - // collect URIs from removed source folders (*not* including those of removed projects) - actualDeletedFiles.addAll(getURIsFromRemovedSourceFolders(changes)); - } + queue(this.dirtyFiles, actualDeletedFiles, actualDirtyFiles); + queue(this.deletedFiles, actualDirtyFiles, actualDeletedFiles); - queue(this.dirtyFiles, actualDeletedFiles, actualDirtyFiles); - queue(this.deletedFiles, actualDirtyFiles, actualDeletedFiles); + // take care of removed projects + Set deletedProjects = new HashSet<>(); + for (ProjectConfigSnapshot prjConfig : changes.getRemovedProjects()) { + deletedProjects.add(prjConfig); + } + for (ProjectConfigSnapshot prjConfig : Iterables.concat(changes.getAddedProjects(), + changes.getChangedProjects())) { + deletedProjects.remove(prjConfig); + } + for (ProjectConfigSnapshot delPrj : deletedProjects) { + ImmutableSet affected = updateResult.oldWorkspaceConfigSnapshot + .getProjectsDependingOn(delPrj.getName()); + this.affectedByDeletedProjects.addAll(affected); + } + handleContentsOfRemovedProjects(updateResult.removedProjectsContents); - // take care of removed projects - Set deletedProjects = new HashSet<>(); - for (ProjectConfigSnapshot prjConfig : changes.getRemovedProjects()) { - deletedProjects.add(prjConfig); - } - for (ProjectConfigSnapshot prjConfig : Iterables.concat(changes.getAddedProjects(), - changes.getChangedProjects())) { - deletedProjects.remove(prjConfig); - } - for (ProjectConfigSnapshot delPrj : deletedProjects) { - ImmutableSet affected = updateResult.oldWorkspaceConfigSnapshot - .getProjectsDependingOn(delPrj.getName()); - this.affectedByDeletedProjects.addAll(affected); - } - handleContentsOfRemovedProjects(updateResult.removedProjectsContents); + if (newRefreshRequest) { + lspLogger.log("Refresh done (" + stopwatch.toString() + "; " + + "projects added/removed: " + changes.getAddedProjects().size() + "/" + + changes.getRemovedProjects().size() + "; " + + "files dirty/deleted: " + dirtyFiles.size() + "/" + deletedFiles.size() + ")."); - if (newRefreshRequest) { - lspLogger.log("Refresh done (" + stopwatch.toString() + "; " - + "projects added/removed: " + changes.getAddedProjects().size() + "/" - + changes.getRemovedProjects().size() + "; " - + "files dirty/deleted: " + dirtyFiles.size() + "/" + deletedFiles.size() + ")."); + progress.update("refreshing", 1); + } - updateProgress("Refreshing", 1); - } + for (String cyclicProject : updateResult.cyclicProjectsAdded) { + ProjectConfigSnapshot projectConfig = workspaceManager.getWorkspaceConfig() + .findProjectByID(cyclicProject); + dirtyFiles.addAll(projectConfig.getProjectDescriptionUris()); + } - for (String cyclicProject : updateResult.cyclicProjectsAdded) { - ProjectConfigSnapshot projectConfig = workspaceManager.getWorkspaceConfig().findProjectByID(cyclicProject); - dirtyFiles.addAll(projectConfig.getProjectDescriptionUris()); - } + for (String cyclicProject : updateResult.cyclicProjectsRemoved) { + // source files of cyclic projects are ignored. Since the cycle is removed now, build these sources. + ProjectConfigSnapshot projectConfig = workspaceManager.getWorkspaceConfig() + .findProjectByID(cyclicProject); + for (SourceFolderSnapshot srcFld : projectConfig.getSourceFolders()) { + dirtyFiles.addAll(srcFld.getAllResources(scanner)); // includes project description + } + } - for (String cyclicProject : updateResult.cyclicProjectsRemoved) { - // source files of cyclic projects are ignored. Since the cycle is removed now, build these sources. - ProjectConfigSnapshot projectConfig = workspaceManager.getWorkspaceConfig().findProjectByID(cyclicProject); - for (SourceFolderSnapshot srcFld : projectConfig.getSourceFolders()) { - dirtyFiles.addAll(srcFld.getAllResources(scanner)); // includes project description + if (dirtyFiles.isEmpty() && deletedFiles.isEmpty() && affectedByDeletedProjects.isEmpty()) { + progress.end("empty change set."); + return new ResourceDescriptionChangeEvent(Collections.emptyList()); } - } - if (dirtyFiles.isEmpty() && deletedFiles.isEmpty() && affectedByDeletedProjects.isEmpty()) { - endProgress("Empty change set."); - return new ResourceDescriptionChangeEvent(Collections.emptyList()); + return doIncrementalBuild(progress, cancelIndicator); } - - return doIncrementalBuild(cancelIndicator); } /** @return list of URIs from newly added source folders (including source folders of added projects). */ @@ -498,12 +450,12 @@ protected void handleContentsOfRemovedProjects(Iterable dirtyFilesToBuild = new LinkedHashSet<>(this.dirtyFiles); Set deletedFilesToBuild = new LinkedHashSet<>(this.deletedFiles); @@ -516,7 +468,7 @@ private IResourceDescription.Event doIncrementalBuild(CancelIndicator cancelIndi BuildOrderIterator pboIterator = buildOrderFactory.createBuildOrderIterator(workspaceConfig, changedPCs); pboIterator.visit(affectedByDeletedProjects); - postCreateListener = getPostCreateListener(workspaceConfig, null, project2dirty, project2deleted); + postCreateListener = getPostCreateListener(progress, workspaceConfig, null, project2dirty, project2deleted); buildRequestFactory.addOnPostCreateListener(postCreateListener); while (pboIterator.hasNext()) { @@ -553,14 +505,13 @@ private IResourceDescription.Event doIncrementalBuild(CancelIndicator cancelIndi pboIterator.visitAffected(newlyBuiltDeltas); } - stopwatch.stop(); List result = toBeConsideredDeltas; onBuildDone(false, false, postCreateListener, Optional.absent()); - endProgress("Build done."); - lspLogger.log("Build done. (" + stopwatch.toString() + ")."); + progress.endWithTime("build done."); + lspLogger.log("Build done. (" + progress.stopwatch.toString() + ")."); return new ResourceDescriptionChangeEvent(result); } catch (Throwable th) { @@ -569,14 +520,14 @@ private IResourceDescription.Event doIncrementalBuild(CancelIndicator cancelIndi onBuildDone(false, wasCanceled, postCreateListener, Optional.of(th)); if (wasCanceled) { - endProgress("Build canceled."); + progress.end("build canceled."); operationCanceledManager.propagateIfCancelException(th); // returns here } // unknown exception or error (and not a cancellation case): // QueueExecutorService will log this as an error with stack trace, so here we just use #log(): - endProgress("Build ABORTED due to exception."); + progress.end("build ABORTED due to exception."); lspLogger.log("Build ABORTED due to exception: " + th.getMessage()); throw th; diff --git a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceManager.java b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceManager.java index f8c6ca2e1b..3d9234ea70 100644 --- a/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceManager.java +++ b/plugins/org.eclipse.n4js.xtext.ide/src/org/eclipse/n4js/xtext/ide/server/build/XWorkspaceManager.java @@ -77,6 +77,8 @@ public class XWorkspaceManager { private final Map projectID2ProjectBuilder = new HashMap<>(); + private URI baseDir; + private XIWorkspaceConfig workspaceConfig; private WorkspaceConfigSnapshot workspaceConfigSnapshot; @@ -113,11 +115,6 @@ public UpdateResult(WorkspaceConfigSnapshot oldWorkspaceConfigSnapshot, Workspac } } - /** Reinitialize a workspace at the current location. */ - public void reinitialize() { - initialize(getBaseDir()); - } - /** * Initialize a workspace at the given location. * @@ -125,7 +122,13 @@ public void reinitialize() { * the location */ public void initialize(URI newBaseDir) { - setWorkspaceConfig(workspaceConfigFactory.createWorkspaceConfig(newBaseDir)); + baseDir = newBaseDir; + } + + /** Scans the workspace at the current location. */ + public void createWorkspaceConfig() { + initialize(getBaseDir()); + setWorkspaceConfig(workspaceConfigFactory.createWorkspaceConfig(getBaseDir())); } /** @@ -267,7 +270,7 @@ public WorkspaceConfigSnapshot getWorkspaceConfig() { /** @return the current base directory {@link URI} */ public URI getBaseDir() { if (this.workspaceConfig == null) { - return null; + return baseDir; } return this.workspaceConfig.getPath(); } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonHelper.java index 725a6ab499..f07e4d51da 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonHelper.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/PackageJsonHelper.java @@ -388,26 +388,26 @@ private void convertDependencies(ProjectDescriptionBuilder target, List existingProjectNames = new HashSet<>(); + Set existingPackageNames = new HashSet<>(); if (avoidDuplicates) { for (ProjectDependency pd : target.getDependencies()) { - existingProjectNames.add(pd.getPackageName()); + existingPackageNames.add(pd.getPackageName()); } } for (NameValuePair pair : depPairs) { - String projectName = pair.getName(); + String packageName = pair.getName(); boolean addProjectDependency = true; - addProjectDependency &= projectName != null && !projectName.isEmpty(); - addProjectDependency &= !(avoidDuplicates && existingProjectNames.contains(projectName)); - existingProjectNames.add(projectName); + addProjectDependency &= packageName != null && !packageName.isEmpty(); + addProjectDependency &= !(avoidDuplicates && existingPackageNames.contains(packageName)); + existingPackageNames.add(packageName); if (addProjectDependency) { JSONValue value = pair.getValue(); String valueStr = asStringOrNull(value); NPMVersionRequirement versionRequirement = valueStr != null ? semverHelper.parse(valueStr) : null; - ProjectDependency dep = new ProjectDependency(projectName, type, valueStr, versionRequirement); + ProjectDependency dep = new ProjectDependency(packageName, type, valueStr, versionRequirement); target.addDependency(dep); } } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/projectDescription/ProjectReference.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/projectDescription/ProjectReference.java index 421101de1c..c5c8865bed 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/projectDescription/ProjectReference.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/packagejson/projectDescription/ProjectReference.java @@ -31,7 +31,7 @@ public String getPackageName() { return packageName; } - public N4JSPackageName getN4JSProjectName() { + public N4JSPackageName getN4JSPackageName() { return packageName != null ? new N4JSPackageName(packageName) : null; } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/accessModifiers/AbstractTypeVisibilityChecker.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/accessModifiers/AbstractTypeVisibilityChecker.java index 9e0b35e7d9..d017c42ef5 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/accessModifiers/AbstractTypeVisibilityChecker.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/scoping/accessModifiers/AbstractTypeVisibilityChecker.java @@ -13,6 +13,7 @@ import static java.util.Collections.emptyList; import java.util.Collection; +import java.util.Objects; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.n4js.packagejson.projectDescription.ProjectReference; @@ -218,31 +219,27 @@ private boolean isProjectVisible(Resource contextResource, final IEObjectDescrip } /** - * Returns with {@code true} if the context module argument belongs to a {@link ProjectType#TEST test} project and - * any of its tested projects contains the element module argument. + * Returns {@code true} if the context module argument belongs to a {@link ProjectType#TEST test} project and any of + * its tested projects contains the element module argument. * * @param contextModule * the content module. - * @param elementModule + * @param importedModule * the element module. * @return {@code true} if the element module's container project is the tested project of the context module. * Otherwise returns with {@code false}. */ - public boolean isTestedProjectOf(final TModule contextModule, final TModule elementModule) { - if (null == elementModule || null == contextModule || null == elementModule.eResource() + public boolean isTestedProjectOf(final TModule contextModule, final TModule importedModule) { + if (null == importedModule || null == contextModule || null == importedModule.eResource() || null == contextModule.eResource()) { return false; } - for (final ProjectReference testedProject : getTestedProjects(contextModule.eResource())) { - final Resource eResource = elementModule.eResource(); - if (null != eResource) { - final N4JSProjectConfigSnapshot elementProject = workspaceAccess.findProjectContaining(eResource); - if (null != elementProject) { - String projectId = elementProject.getProjectIdForPackageName(testedProject.getPackageName()); - if (elementProject.getName().equals(projectId)) { - return true; - } + N4JSProjectConfigSnapshot importedProject = workspaceAccess.findProjectContaining(importedModule.eResource()); + if (null != importedProject) { + for (ProjectReference testedProjectRef : getTestedProjects(contextModule.eResource())) { + if (Objects.equals(importedProject.getPackageName(), testedProjectRef.getPackageName())) { + return true; } } } @@ -263,7 +260,7 @@ public boolean isTestedProjectOf(final TModule contextModule, final TModule elem */ public boolean isTestedProjectOf(final TModule contextModule, final N4JSProjectConfigSnapshot elementProject) { for (final ProjectReference testedProject : getTestedProjects(contextModule.eResource())) { - if (elementProject.getName().equals(testedProject.getPackageName())) { + if (Objects.equals(elementProject.getPackageName(), testedProject.getPackageName())) { return true; } } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ApiImplMapping.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ApiImplMapping.java index fdaefeee43..379651c2d1 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ApiImplMapping.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ApiImplMapping.java @@ -89,7 +89,7 @@ public static ApiImplMapping of(N4JSWorkspaceConfigSnapshot wc) { final ApiImplMapping mapping = new ApiImplMapping(); for (N4JSProjectConfigSnapshot pImpl : wc.getProjects()) { for (ProjectReference pApi : pImpl.getProjectDescription().getImplementedProjects()) { - N4JSProjectConfigSnapshot pApiResolved = wc.findProjectByID(pApi.getPackageName()); + N4JSProjectConfigSnapshot pApiResolved = wc.findProjectByPackageName(pApi.getN4JSPackageName()); if (pApiResolved != null) { mapping.put(pApiResolved, pImpl); } @@ -139,7 +139,7 @@ public ApiImplMapping enhance(N4JSWorkspaceConfigSnapshot wc, for (N4JSProjectConfigSnapshot pImpl : implProjects) { for (ProjectReference pApiRef : pImpl.getProjectDescription().getImplementedProjects()) { - N4JSProjectConfigSnapshot pApi = wc.findProjectByID(pApiRef.getPackageName()); + N4JSProjectConfigSnapshot pApi = wc.findProjectByPackageName(pApiRef.getN4JSPackageName()); if (pApi != null) { // note: #getImplementedProjects() will return implemented projects from entire workspace, // so we here have to make sure pApi is contained in apiProjects diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ProjectCompareHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ProjectCompareHelper.java index d28843a08f..4a58062698 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ProjectCompareHelper.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/compare/ProjectCompareHelper.java @@ -299,7 +299,8 @@ public ProjectComparisonEntry compareModules(TModule module, N4JSPackageName imp List apiProjects = new ArrayList<>(); for (ProjectReference apiProjectRef : implProject.getProjectDescription().getImplementedProjects()) { - N4JSProjectConfigSnapshot currApiProject = wc.findProjectByID(apiProjectRef.getPackageName()); + N4JSProjectConfigSnapshot currApiProject = wc + .findProjectByPackageName(apiProjectRef.getN4JSPackageName()); if (currApiProject != null) { apiProjects.add(currApiProject); } diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/N4JSLanguageHelper.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/N4JSLanguageHelper.java index eededf7943..c98ff7b566 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/N4JSLanguageHelper.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/utils/N4JSLanguageHelper.java @@ -13,13 +13,13 @@ import java.util.Collection; import java.util.stream.Collectors; -import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.n4js.N4JSGlobals; import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.ImportDeclaration; import org.eclipse.n4js.packagejson.projectDescription.ProjectDescription; import org.eclipse.n4js.packagejson.projectDescription.ProjectType; import org.eclipse.n4js.services.N4JSGrammarAccess; @@ -120,17 +120,19 @@ public boolean isOpaqueModule(Resource resource) { * tells how to deal with error cases: true means null will be returned, * false means the given project will be returned. */ - public N4JSProjectConfigSnapshot replaceDefinitionProjectByDefinedProject(Notifier context, - N4JSProjectConfigSnapshot project, boolean returnNullOnError) { - if (project != null && project.getType() == ProjectType.DEFINITION) { - N4JSPackageName definedPackageName = project.getDefinesPackage(); - if (definedPackageName != null) { - String definedProjectId = project.getProjectIdForPackageName(definedPackageName.getRawName()); - if (definedProjectId != null) { - N4JSProjectConfigSnapshot definedProject = workspaceAccess.findProjectByName(context, - definedProjectId); - if (definedProject != null) { - return definedProject; + public N4JSProjectConfigSnapshot replaceDefinitionProjectByDefinedProject(ImportDeclaration importingDeclOrigAST, + N4JSProjectConfigSnapshot importedProject, boolean returnNullOnError) { + + if (importedProject != null && importedProject.getType() == ProjectType.DEFINITION) { + N4JSPackageName definitionPackageName = importedProject.getDefinesPackage(); + N4JSProjectConfigSnapshot importingPrj = workspaceAccess.findProjectContaining(importingDeclOrigAST); + if (definitionPackageName != null && importingPrj != null) { + String definitionProjectId = importingPrj.getProjectIdForPackageName(definitionPackageName.getRawName()); + if (definitionProjectId != null) { + N4JSProjectConfigSnapshot definitionProject = workspaceAccess.findProjectByName(importingDeclOrigAST, + definitionProjectId); + if (definitionProject != null) { + return definitionProject; } } } @@ -138,7 +140,7 @@ public N4JSProjectConfigSnapshot replaceDefinitionProjectByDefinedProject(Notifi return null; } } - return project; + return importedProject; } /** @@ -146,9 +148,9 @@ public N4JSProjectConfigSnapshot replaceDefinitionProjectByDefinedProject(Notifi * {@code require()}, etc. *

* WARNING: regarding {@code index} the same warning applies as given - * {@link #getOutputFileExtension(IResourceDescriptions, TModule) here}. + * {@link #getOutputFileExtension(IResourceDescriptions, ImportDeclaration, TModule) here}. */ - public boolean isES6Module(IResourceDescriptions index, TModule module) { + public boolean isES6Module(IResourceDescriptions index, ImportDeclaration importingDeclOrigAST, TModule module) { // 1) decide based on the file extension of the target module Resource resource = module.eResource(); URI uri = resource != null ? resource.getURI() : null; @@ -156,7 +158,7 @@ public boolean isES6Module(IResourceDescriptions index, TModule module) { if (!module.isN4jsdModule() && N4JSGlobals.ALL_N4JS_FILE_EXTENSIONS.contains(ext)) { return true; // the N4JS transpiler always emits ES6 module code } - String extActual = getOutputFileExtension(index, module); + String extActual = getOutputFileExtension(index, importingDeclOrigAST, module); if (N4JSGlobals.CJS_FILE_EXTENSION.equals(extActual)) { return false; } else if (N4JSGlobals.MJS_FILE_EXTENSION.equals(extActual)) { @@ -170,7 +172,7 @@ public boolean isES6Module(IResourceDescriptions index, TModule module) { // (failed: file extension of target module does not tell whether it's commonjs or esm) // 2) decide based on the nature of the target project - N4JSProjectConfigSnapshot targetProject = replaceDefinitionProjectByDefinedProject(resource, + N4JSProjectConfigSnapshot targetProject = replaceDefinitionProjectByDefinedProject(importingDeclOrigAST, workspaceAccess.findProjectContaining(resource), true); if (targetProject == null) { return true; // use 'true' as fall back @@ -197,10 +199,12 @@ public boolean isES6Module(IResourceDescriptions index, TModule module) { * given {@code module} as context will not suffice, because definition projects might not have a dependency to * their defined project! */ - public String getOutputFileExtension(IResourceDescriptions index, TModule targetModule) { + public String getOutputFileExtension(IResourceDescriptions index, ImportDeclaration importingDeclOrigAST, + TModule targetModule) { + if (targetModule.isN4jsdModule()) { // in case of .n4jsd files it is more tricky: - return getActualFileExtensionForN4jsdFile(index, targetModule); + return getActualFileExtensionForN4jsdFile(index, importingDeclOrigAST, targetModule); } Resource targetResource = targetModule.eResource(); URI uri = targetResource != null ? targetResource.getURI() : null; @@ -222,7 +226,9 @@ public String getOutputFileExtension(IResourceDescriptions index, TModule target * In case of .n4jsd files, we have to find out the extension of the plain-JS file being described by the .n4jsd * file *and* provide special handling for directory imports. */ - private String getActualFileExtensionForN4jsdFile(IResourceDescriptions index, TModule targetModule) { + private String getActualFileExtensionForN4jsdFile(IResourceDescriptions index, ImportDeclaration importingDeclOrigAST, + TModule targetModule) { + QualifiedName targetQN = qualifiedNameConverter.toQualifiedName(targetModule.getQualifiedName()); Iterable matchingTModules = index.getExportedObjects(TypesPackage.Literals.TMODULE, targetQN, false); @@ -251,7 +257,7 @@ private String getActualFileExtensionForN4jsdFile(IResourceDescriptions index, T } // no plain JS file found, check for "directory import" - if (isDirectoryWithPackageJson(index, targetModule, targetQN)) { + if (isDirectoryWithPackageJson(index, importingDeclOrigAST, targetModule, targetQN)) { return ""; // no file extension for directory imports } @@ -259,8 +265,8 @@ private String getActualFileExtensionForN4jsdFile(IResourceDescriptions index, T return N4JSGlobals.JS_FILE_EXTENSION; } - private boolean isDirectoryWithPackageJson(IResourceDescriptions index, TModule targetModule, - QualifiedName targetQN) { + private boolean isDirectoryWithPackageJson(IResourceDescriptions index, ImportDeclaration importingDeclOrigAST, + TModule targetModule, QualifiedName targetQN) { // NOTE: the following approach would be a more elegant implementation of this method, but would require a // different computation of FQNs for package.json files in source folders (in N4JSQualifiedNameProvider): @@ -273,7 +279,7 @@ private boolean isDirectoryWithPackageJson(IResourceDescriptions index, TModule // } // @formatter:on - N4JSProjectConfigSnapshot targetProject = replaceDefinitionProjectByDefinedProject(targetModule, + N4JSProjectConfigSnapshot targetProject = replaceDefinitionProjectByDefinedProject(importingDeclOrigAST, workspaceAccess.findProjectContaining(targetModule), true); if (targetProject == null) { return false; diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/packagejson/N4JSProjectSetupJsonValidatorExtension.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/packagejson/N4JSProjectSetupJsonValidatorExtension.java index 9959365969..28ec18403f 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/packagejson/N4JSProjectSetupJsonValidatorExtension.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/packagejson/N4JSProjectSetupJsonValidatorExtension.java @@ -1285,7 +1285,17 @@ private List getReferencesFromDependenciesObject(JSO if (pair.getValue() instanceof JSONStringLiteral) { JSONStringLiteral stringLit = (JSONStringLiteral) pair.getValue(); String prjName = pair.getName(); - String prjID = currentProject.getProjectIdForPackageName(prjName); + String prjID = prjName; + if (currentProject.isKnownDependency(prjName)) { + prjID = currentProject.getProjectIdForPackageName(prjName); + } else { + N4JSWorkspaceConfigSnapshot wsConfig = workspaceAccess.getWorkspaceConfig(getDocument()); + N4JSPackageName pckName = N4JSPackageName.create(prjName); + N4JSProjectConfigSnapshot depPrj = wsConfig.findProjectByPackageName(pckName); + if (depPrj != null) { + prjID = depPrj.getName(); + } + } IParseResult parseResult = semverHelper.getParseResult(stringLit.getValue()); NPMVersionRequirement npmVersion = semverHelper.parse(parseResult); ValidationProjectReference vpr = new ValidationProjectReference(prjName, prjID, npmVersion, @@ -1386,7 +1396,8 @@ private void checkReference(ValidationProjectReference ref, Map allNames = new HashSet<>(workspace.getAllPackageNames()); + HashSet allNames = new LinkedHashSet<>(); semanticDeps.stream().forEach(d -> allNames.add(d.getPackageName())); + projectDescription.getImplementedProjects().stream().forEach(d -> allNames.add(d.getPackageName())); - packageNameToProjectIds = Collections - .unmodifiableMap(semanticDependencySupplier.computePackageName2ProjectIdMap( - workspace, projectDescription, relatedRootLocation, allNames)); + packageNameToProjectIds = semanticDependencySupplier.computePackageName2ProjectIdMap(workspace, + projectDescription, relatedRootLocation, allNames); + packageNameToProjectIds = Collections.unmodifiableMap(packageNameToProjectIds); List result = new ArrayList<>(semanticDeps.size()); for (ProjectDependency sdep : semanticDeps) { @@ -265,6 +266,7 @@ protected void init() { result.add(newDep); } semanticDependencies = Collections.unmodifiableList(result); + } @Override diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSProjectConfigSnapshot.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSProjectConfigSnapshot.java index d8956fe205..dadc460e66 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSProjectConfigSnapshot.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSProjectConfigSnapshot.java @@ -47,9 +47,9 @@ import org.eclipse.xtext.xbase.lib.IterableExtensions; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -61,7 +61,7 @@ public class N4JSProjectConfigSnapshot extends ProjectConfigSnapshot { private final ProjectDescription projectDescription; - private final ImmutableMap packageNameToProjectIds; + private final ImmutableBiMap packageNameToProjectIds; private final boolean external; /** Creates a new {@link N4JSProjectConfigSnapshot}. */ @@ -74,7 +74,7 @@ public N4JSProjectConfigSnapshot(ProjectDescription projectDescription, URI path indexOnly, generatorEnabled, dependencies, sourceFolders); this.projectDescription = Objects.requireNonNull(projectDescription); - this.packageNameToProjectIds = ImmutableMap.copyOf(packageNameToProjectIds); + this.packageNameToProjectIds = ImmutableBiMap.copyOf(packageNameToProjectIds); this.external = isDirectlyLocatedInNodeModulesFolder(path); } @@ -169,8 +169,16 @@ public String getProjectIdForPackageName(String packageName) { * Returns true iff the given packageName can be resolved to a project id. Usually it cannot be resolved for ignored * dependencies of plain-js projects and returns false in those cases. */ - public boolean isKnownDependency(String packageName) { - return !packageNameToProjectIds.containsKey(packageName); + public boolean isKnownDependency(N4JSPackageName packageName) { + return packageNameToProjectIds.containsKey(packageName.toString()); + } + + /** + * Returns true iff the given project id can be resolved. Usually it cannot be resolved for ignored dependencies of + * plain-js projects and returns false in those cases. + */ + public boolean isKnownDependency(String projectId) { + return packageNameToProjectIds.containsValue(projectId); } // ============================================================================================================== diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSWorkspaceConfigSnapshot.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSWorkspaceConfigSnapshot.java index 8a1020c960..8ab5017da0 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSWorkspaceConfigSnapshot.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/N4JSWorkspaceConfigSnapshot.java @@ -10,9 +10,15 @@ */ package org.eclipse.n4js.workspace; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + import org.eclipse.emf.common.util.URI; import org.eclipse.n4js.packagejson.projectDescription.ModuleFilterType; +import org.eclipse.n4js.workspace.utils.N4JSPackageName; import org.eclipse.n4js.xtext.workspace.BuildOrderInfo; +import org.eclipse.n4js.xtext.workspace.ProjectConfigSnapshot; import org.eclipse.n4js.xtext.workspace.ProjectSet; import org.eclipse.n4js.xtext.workspace.WorkspaceConfigSnapshot; import org.eclipse.xtext.xbase.lib.Pair; @@ -29,9 +35,28 @@ public class N4JSWorkspaceConfigSnapshot extends WorkspaceConfigSnapshot { public static final N4JSWorkspaceConfigSnapshot EMPTY = new N4JSWorkspaceConfigSnapshot(EMPTY_PATH, ProjectSet.EMPTY, BuildOrderInfo.NULL); + /** Maps package names to projects. In case of name collisions only one is mapped. */ + // TODO: Move to {@link ProjectSet} when development setup allows + protected final Map packageName2Project; + /** Creates a {@link N4JSWorkspaceConfigSnapshot}. */ public N4JSWorkspaceConfigSnapshot(URI path, ProjectSet projects, BuildOrderInfo buildOrderInfo) { super(path, projects, buildOrderInfo); + packageName2Project = computeMap(projects); + } + + static private Map computeMap(ProjectSet projects) { + Map packageName2Project = new LinkedHashMap<>(); + for (ProjectConfigSnapshot prj : projects.getProjects()) { + N4JSProjectConfigSnapshot n4jsPCS = (N4JSProjectConfigSnapshot) prj; + packageName2Project.put(n4jsPCS.getN4JSPackageName(), n4jsPCS); + } + return Collections.unmodifiableMap(packageName2Project); + } + + /** Returns a project for package name. In case of name collisions one project is returned. */ + public N4JSProjectConfigSnapshot findProjectByPackageName(N4JSPackageName name) { + return packageName2Project.get(name); } @Override diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/WorkspaceAccess.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/WorkspaceAccess.java index 3d880a9a9d..99ed3cf6c3 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/WorkspaceAccess.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/WorkspaceAccess.java @@ -79,7 +79,7 @@ public N4JSProjectConfigSnapshot findProjectByPath(Notifier context, URI path) { /** Convenience for {@link N4JSWorkspaceConfigSnapshot#findProjectByID(String)}. */ public N4JSProjectConfigSnapshot findProjectByName(Notifier context, N4JSPackageName packageName) { - return packageName != null ? findProjectByName(context, packageName.getRawName()) : null; + return packageName != null ? getWorkspaceConfig(context).findProjectByPackageName(packageName) : null; } /** Convenience for {@link N4JSWorkspaceConfigSnapshot#findProjectByID(String)}. */ diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/utils/SemanticDependencySupplier.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/utils/SemanticDependencySupplier.java index 28534bde28..4e9504877e 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/utils/SemanticDependencySupplier.java +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/workspace/utils/SemanticDependencySupplier.java @@ -62,7 +62,7 @@ public List computeSemanticDependencies(DefinitionProjectMap boolean sawDefinitionsOnly = true; for (ProjectDependency dependency : dependencies) { - N4JSPackageName dependencyName = dependency.getN4JSProjectName(); + N4JSPackageName dependencyName = dependency.getN4JSPackageName(); existingDependencies.add(dependencyName.getRawName()); Collection defPrjDeps = definitionProjects.getDefinitionProjects(dependencyName); for (N4JSPackageName prjName : defPrjDeps) { @@ -108,7 +108,7 @@ public List computeSemanticDependencies(DefinitionProjectMap List typesDefDeps = new ArrayList<>(); for (Iterator iter = moveToTop.iterator(); iter.hasNext();) { ProjectDependency topDep = iter.next(); - if (Objects.equals(N4JSGlobals.TYPES_SCOPE, topDep.getN4JSProjectName().getScopeName())) { + if (Objects.equals(N4JSGlobals.TYPES_SCOPE, topDep.getN4JSPackageName().getScopeName())) { iter.remove(); typesDefDeps.add(topDep); } @@ -178,7 +178,8 @@ private Path getPathToDependency(Path relatedRootLocation, Path projectLocation, } // Second check if it is a dependency in the node_modules folder - for (File nodeModulesDir : nodeModulesFolder.getNodeModulesFoldersInOrderOfPriority()) { + List nmFolders = nodeModulesFolder.getNodeModulesFoldersInOrderOfPriority(); + for (File nodeModulesDir : nmFolders) { Path absDepPath = nodeModulesDir.toPath().resolve(depName); absDepPath = resolveSymbolicLinkOrDefault(absDepPath); if (candidatePaths.contains(absDepPath)) { @@ -247,7 +248,6 @@ public static Path resolveSymbolicLink(Path path) { } } return null; - } /** diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderGenerateTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderGenerateTest.java index 149eea22eb..a712fc2340 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderGenerateTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/builder/IncrementalBuilderGenerateTest.java @@ -479,7 +479,7 @@ public void testCleanAndRebuild() { assertFalse(projectStateInNodeModules.exists()); assertFalse(projectStateInOrdinaryProject.exists()); - languageServer.getFrontend().reinitWorkspace(); + languageServer.getFrontend().rebuildWorkspace(); joinServerRequests(); assertFalse(outputFileInNodeModules.exists()); diff --git a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/server/CommandRebuildTest.java b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/server/CommandRebuildTest.java index 94c10d98d0..fd660c1124 100644 --- a/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/server/CommandRebuildTest.java +++ b/tests/org.eclipse.n4js.ide.tests/src/org/eclipse/n4js/ide/tests/server/CommandRebuildTest.java @@ -97,7 +97,7 @@ public void testRebuildWithoutClean() throws Exception { test("class A { foo(a: A) { } } class Main { main(a: A) { a.foo(null); } }"); // send command under test - languageServer.getFrontend().reinitWorkspace(); + languageServer.getFrontend().rebuildWorkspace(); // wait for previous command to finish joinServerRequests();