From 6fe3662d995e9bc0bda8b84d4dd762edc37fd908 Mon Sep 17 00:00:00 2001 From: Maruf Rasully <100434800+marufrasully@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:31:49 +0200 Subject: [PATCH] fix: lsp for adaption project - first iteration (#733) * fix: lsp for adaption project - first iteration * fix: enhance get context api test --- .changeset/nasty-bugs-chew.md | 9 ++ packages/context/src/adp-manifest.ts | 11 ++ packages/context/src/api.ts | 11 +- packages/context/src/utils/view-files.ts | 10 +- .../context/test/unit/adp-manifest.test.ts | 55 +++++++++ packages/context/test/unit/api.test.ts | 5 + packages/context/test/unit/loader.test.ts | 8 +- .../test/unit/utils/view-files.test.ts | 19 +++ packages/language-server/package.json | 1 + .../xml-view-diagnostics.test.ts.snap | 72 +++++++++++ .../test/unit/xml-view-diagnostics.test.ts | 114 ++++++++++++++++++ .../test/manual-tests/unique-control-ids.md | 22 ++++ .../framework/projects/adp.test/.gitignore | 5 + .../projects/adp.test/AppVariant_index.html | 64 ++++++++++ .../framework/projects/adp.test/README.md | 3 + .../framework/projects/adp.test/package.json | 22 ++++ .../framework/projects/adp.test/ui5.yaml | 44 +++++++ .../fragments/actionToolbar.fragment.xml | 4 + .../changes/fragments/filterBar.fragment.xml | 4 + .../id_1728329677838_297_addXML.change | 30 +++++ .../id_1728329742822_298_addXML.change | 30 +++++ .../adp.test/webapp/i18n/i18n.properties | 9 ++ .../adp.test/webapp/manifest.appdescr_variant | 49 ++++++++ test-packages/framework/src/types.ts | 2 + 24 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 .changeset/nasty-bugs-chew.md create mode 100644 packages/context/src/adp-manifest.ts create mode 100644 packages/context/test/unit/adp-manifest.test.ts create mode 100644 packages/language-server/test/unit/__snapshots__/xml-view-diagnostics.test.ts.snap create mode 100644 packages/language-server/test/unit/xml-view-diagnostics.test.ts create mode 100644 test-packages/framework/projects/adp.test/.gitignore create mode 100644 test-packages/framework/projects/adp.test/AppVariant_index.html create mode 100644 test-packages/framework/projects/adp.test/README.md create mode 100644 test-packages/framework/projects/adp.test/package.json create mode 100644 test-packages/framework/projects/adp.test/ui5.yaml create mode 100644 test-packages/framework/projects/adp.test/webapp/changes/fragments/actionToolbar.fragment.xml create mode 100644 test-packages/framework/projects/adp.test/webapp/changes/fragments/filterBar.fragment.xml create mode 100644 test-packages/framework/projects/adp.test/webapp/changes/id_1728329677838_297_addXML.change create mode 100644 test-packages/framework/projects/adp.test/webapp/changes/id_1728329742822_298_addXML.change create mode 100644 test-packages/framework/projects/adp.test/webapp/i18n/i18n.properties create mode 100644 test-packages/framework/projects/adp.test/webapp/manifest.appdescr_variant diff --git a/.changeset/nasty-bugs-chew.md b/.changeset/nasty-bugs-chew.md new file mode 100644 index 000000000..f87b1c29a --- /dev/null +++ b/.changeset/nasty-bugs-chew.md @@ -0,0 +1,9 @@ +--- +"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch +"vscode-ui5-language-assistant": patch +"@ui5-language-assistant/language-server": patch +"@ui5-language-assistant/test-framework": patch +"@ui5-language-assistant/context": patch +--- + +fix: lsp for adaption project - first iteration diff --git a/packages/context/src/adp-manifest.ts b/packages/context/src/adp-manifest.ts new file mode 100644 index 000000000..6cc59ff53 --- /dev/null +++ b/packages/context/src/adp-manifest.ts @@ -0,0 +1,11 @@ +import findUp from "find-up"; +import { FileName } from "@sap-ux/project-access"; +/** + * Get path of a manifest.appdescr_variant file for adaption project. + * @param documentPath path to a file e.g. absolute/path/webapp/ext/main/Main.view.xml + */ +export async function finAdpdManifestPath( + documentPath: string +): Promise { + return findUp(FileName.ManifestAppDescrVar, { cwd: documentPath }); +} diff --git a/packages/context/src/api.ts b/packages/context/src/api.ts index 1e8bc6201..f8eef92db 100644 --- a/packages/context/src/api.ts +++ b/packages/context/src/api.ts @@ -4,6 +4,7 @@ import { getManifestDetails, getUI5Manifest, } from "./manifest"; +import { finAdpdManifestPath } from "./adp-manifest"; import { getServices } from "./services"; import { Context } from "./types"; import { getSemanticModel } from "./ui5-model"; @@ -47,7 +48,8 @@ export async function getContext( ): Promise { try { const manifestDetails = await getManifestDetails(documentPath); - const manifest = await getUI5Manifest(manifestDetails.manifestPath); + let manifestPath = manifestDetails.manifestPath; + const manifest = await getUI5Manifest(manifestPath); let minUI5Version = manifestDetails.minUI5Version; if (manifest) { minUI5Version = getMinimumUI5Version(manifest); @@ -61,7 +63,12 @@ export async function getContext( ); const services = await getServices(documentPath); const customViewId = await getCustomViewId(documentPath); - const manifestPath = manifestDetails.manifestPath; + if (!manifestPath) { + const adpManifestPath = await finAdpdManifestPath(documentPath); + if (adpManifestPath) { + manifestPath = adpManifestPath; + } + } const viewFiles = await getViewFiles({ manifestPath, documentPath, diff --git a/packages/context/src/utils/view-files.ts b/packages/context/src/utils/view-files.ts index e9062d0aa..bdae03a90 100644 --- a/packages/context/src/utils/view-files.ts +++ b/packages/context/src/utils/view-files.ts @@ -70,7 +70,15 @@ export async function getViewFiles(param: { } const files = {}; - await processViewFiles(join(manifestPath, ".."), files); + if (!manifestPath) { + // a project without manifest.json or manifest.appdescr_variant file. Only support for current view file + const ast = await createDocumentAst(documentPath); + files[documentPath] = ast; + } else { + // find all view files + await processViewFiles(join(manifestPath, ".."), files); + } + cache.setViewFiles(manifestPath, files); return files; } diff --git a/packages/context/test/unit/adp-manifest.test.ts b/packages/context/test/unit/adp-manifest.test.ts new file mode 100644 index 000000000..6a8f99b38 --- /dev/null +++ b/packages/context/test/unit/adp-manifest.test.ts @@ -0,0 +1,55 @@ +import { join } from "path"; +import { + Config, + ProjectName, + ProjectType, + TestFramework, +} from "@ui5-language-assistant/test-framework"; +import { finAdpdManifestPath } from "../../src/adp-manifest"; + +describe("adp-manifest", () => { + let framework: TestFramework; + beforeAll(function () { + const useConfig: Config = { + projectInfo: { + name: ProjectName.cap, + type: ProjectType.CAP, + npmInstall: true, + }, + }; + framework = new TestFramework(useConfig); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + describe("finAdpdManifestPath", () => { + beforeAll(function () { + const useConfig: Config = { + projectInfo: { + name: ProjectName.adp, + type: ProjectType.ADP, + npmInstall: false, + }, + }; + framework = new TestFramework(useConfig); + }); + it("undefined", async () => { + const root = framework.getProjectRoot(); + const result = await finAdpdManifestPath(root); + expect(result).toBeUndefined(); + }); + it("path to manifest.appdescr_variant file", async () => { + const root = framework.getProjectRoot(); + const pathSegments = [ + "webapp", + "changes", + "fragments", + "actionToolbar.fragment.xml", + ]; + const docPath = join(root, ...pathSegments); + const result = await finAdpdManifestPath(docPath); + expect(result).toEqual(join(root, "webapp", "manifest.appdescr_variant")); + }); + }); +}); diff --git a/packages/context/test/unit/api.test.ts b/packages/context/test/unit/api.test.ts index a6f0cc35b..8241da08b 100644 --- a/packages/context/test/unit/api.test.ts +++ b/packages/context/test/unit/api.test.ts @@ -1,4 +1,5 @@ import * as manifest from "../../src/manifest"; +import * as adpManifest from "../../src/adp-manifest"; import * as ui5Yaml from "../../src/ui5-yaml"; import * as ui5Model from "../../src/ui5-model"; import * as services from "../../src/services"; @@ -51,6 +52,9 @@ describe("context", () => { const getServicesStub = jest .spyOn(services, "getServices") .mockResolvedValue({}); + const finAdpdManifestPathStub = jest + .spyOn(adpManifest, "finAdpdManifestPath") + .mockResolvedValue("/path/to/app/variant"); const getViewFilesStub = jest .spyOn(viewFiles, "getViewFiles") .mockResolvedValue({}); @@ -67,6 +71,7 @@ describe("context", () => { expect(getYamlDetailsStub).toHaveBeenCalled(); expect(getSemanticModelStub).toHaveBeenCalled(); expect(getServicesStub).toHaveBeenCalled(); + expect(finAdpdManifestPathStub).toHaveBeenCalled(); expect(getViewFilesStub).toHaveBeenCalled(); expect(getControlIdsStub).toHaveBeenCalled(); expect(result).toContainAllKeys([ diff --git a/packages/context/test/unit/loader.test.ts b/packages/context/test/unit/loader.test.ts index da20ec7b3..233f5fc69 100644 --- a/packages/context/test/unit/loader.test.ts +++ b/packages/context/test/unit/loader.test.ts @@ -11,7 +11,11 @@ import * as manifest from "../../src/manifest"; import * as projectUtils from "../../src/utils/project"; import { cache } from "../../src/cache"; import { Manifest } from "@sap-ux/project-access"; -import { ProjectKind, UI5_PROJECT_TYPE } from "../../src/types"; +import { + ProjectKind, + UI5_PROJECT_TYPE, + ProjectType as ProjType, +} from "../../src/types"; import { getProjectData } from "./utils"; import { getManifestDetails, getUI5Manifest } from "../../src/manifest"; import { getApp } from "../../src/loader"; @@ -156,7 +160,7 @@ describe("loader", () => { projectRoot ); const projectInfo = { kind: "Java", type: "CAP" } as { - type: ProjectType; + type: ProjType; kind: ProjectKind; }; const capProject = await loader.getCAPProject( diff --git a/packages/context/test/unit/utils/view-files.test.ts b/packages/context/test/unit/utils/view-files.test.ts index 6e7903751..94e141372 100644 --- a/packages/context/test/unit/utils/view-files.test.ts +++ b/packages/context/test/unit/utils/view-files.test.ts @@ -104,4 +104,23 @@ describe("view-files", () => { expect(Object.keys(viewFiles).length).toBeGreaterThan(1); expect(viewFiles[documentPath]).toBeDefined(); }); + it("get view files - no manifest path", async () => { + // arrange + cache.reset(); + const getViewFilesStub = jest + .spyOn(cache, "getViewFiles") + .mockReturnValue({}); + const projectRoot = testFramework.getProjectRoot(); + const documentPath = getDocumentPath(projectRoot); + const manifestPath = ""; + // act + const viewFiles = await getViewFiles({ + manifestPath, + documentPath, + }); + // assert + expect(getViewFilesStub).toHaveBeenCalledTimes(1); + expect(Object.keys(viewFiles).length).toEqual(1); + expect(viewFiles[documentPath]).toBeDefined(); + }); }); diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 7d34a6e1a..50edcf056 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -57,6 +57,7 @@ "@types/tmp": "0.2.0", "@ui5-language-assistant/semantic-model-types": "4.0.11", "@ui5-language-assistant/test-utils": "4.0.16", + "@ui5-language-assistant/test-framework": "4.0.12", "string-replace-loader": "3.1.0", "vscode-languageserver-types": "3.17.2" }, diff --git a/packages/language-server/test/unit/__snapshots__/xml-view-diagnostics.test.ts.snap b/packages/language-server/test/unit/__snapshots__/xml-view-diagnostics.test.ts.snap new file mode 100644 index 000000000..efd1033a8 --- /dev/null +++ b/packages/language-server/test/unit/__snapshots__/xml-view-diagnostics.test.ts.snap @@ -0,0 +1,72 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`xml view diagnostics adaptation project diagnostics for duplicate ids - duplicates 1`] = ` +Array [ + Object { + "message": "Select a unique ID. The current \\"_IDGenText\\" ID has already been used.", + "range": Object { + "end": Object { + "character": 25, + "line": 3, + }, + "start": Object { + "character": 13, + "line": 3, + }, + }, + "relatedInformation": Array [ + Object { + "location": Object { + "range": Object { + "end": Object { + "character": 25, + "line": 3, + }, + "start": Object { + "character": 13, + "line": 3, + }, + }, + "uri": "filterBar.fragment.xml", + }, + "message": "An identical ID is also used here.", + }, + ], + "severity": 1, + "source": "UI5 Language Assistant", + }, + Object { + "message": "Select a unique ID. The current \\"_IDGenButton\\" ID has already been used.", + "range": Object { + "end": Object { + "character": 29, + "line": 4, + }, + "start": Object { + "character": 15, + "line": 4, + }, + }, + "relatedInformation": Array [ + Object { + "location": Object { + "range": Object { + "end": Object { + "character": 29, + "line": 4, + }, + "start": Object { + "character": 15, + "line": 4, + }, + }, + "uri": "filterBar.fragment.xml", + }, + "message": "An identical ID is also used here.", + }, + ], + "severity": 1, + "source": "UI5 Language Assistant", + }, +] +`; diff --git a/packages/language-server/test/unit/xml-view-diagnostics.test.ts b/packages/language-server/test/unit/xml-view-diagnostics.test.ts new file mode 100644 index 000000000..19306a5ca --- /dev/null +++ b/packages/language-server/test/unit/xml-view-diagnostics.test.ts @@ -0,0 +1,114 @@ +import { getXMLViewIdDiagnostics } from "../../src/xml-view-diagnostics"; +import { + Config, + ProjectName, + ProjectType, + TestFramework, +} from "@ui5-language-assistant/test-framework"; +import { + Context, + getContext, + isContext, + cache, +} from "@ui5-language-assistant/context"; +import { join, basename } from "path"; + +describe("xml view diagnostics", () => { + describe("adaptation project", () => { + let framework: TestFramework; + let context: Context; + beforeAll(async () => { + const useConfig: Config = { + projectInfo: { + name: ProjectName.adp, + type: ProjectType.ADP, + npmInstall: false, + }, + }; + framework = new TestFramework(useConfig); + }); + beforeEach(() => { + // reset to avoid side effects + cache.reset(); + }); + it("diagnostics for duplicate ids - no duplicate", async () => { + // arrange + const root = framework.getProjectRoot(); + const snippet = ` + + + +