From 6f5945b316d6e823da019c863041848f0820989f Mon Sep 17 00:00:00 2001 From: Maruf Rasully <100434800+marufrasully@users.noreply.github.com> Date: Mon, 27 Jan 2025 13:42:54 +0100 Subject: [PATCH] fix: do not show vSorterInfo as completion item and show correct diagnostic (#741) * fix: vSorterInfo issue * fix: add change set * fix: sonar qube * fix: failing test * fix: update actions/upload-artifact * fix: sonar qube * refactor: function rename and jsdoc --- .changeset/quick-bobcats-doubt.md | 7 + .github/workflows/release.yaml | 2 +- packages/binding/src/definition/definition.ts | 387 +----------- .../binding/src/services/completion/index.ts | 8 +- .../services/completion/providers/binding.ts | 13 +- .../diagnostics/validators/check-required.ts | 4 +- packages/binding/src/utils/definition.ts | 478 +++++++++++++++ packages/binding/src/utils/index.ts | 17 +- .../completion/aggregation-binding.test.ts | 19 + .../__snapshots__/definition.test.ts.snap | 348 +++++++++++ .../test/unit/utils/definition.test.ts | 568 ++++++++++++++++++ packages/constant/src/constant.ts | 4 +- packages/logic-utils/test/unit/ui5.test.ts | 10 +- packages/semantic-model/src/convert.ts | 8 +- .../test/unit/api-negative.test.ts | 9 - packages/semantic-model/test/unit/api.test.ts | 2 +- test-packages/test-utils/api.d.ts | 2 +- .../src/utils/download-ui5-resources.ts | 9 +- .../src/utils/semantic-model-provider.ts | 8 +- 19 files changed, 1478 insertions(+), 425 deletions(-) create mode 100644 .changeset/quick-bobcats-doubt.md create mode 100644 packages/binding/src/utils/definition.ts create mode 100644 packages/binding/test/unit/utils/__snapshots__/definition.test.ts.snap create mode 100644 packages/binding/test/unit/utils/definition.test.ts diff --git a/.changeset/quick-bobcats-doubt.md b/.changeset/quick-bobcats-doubt.md new file mode 100644 index 000000000..2d40681c0 --- /dev/null +++ b/.changeset/quick-bobcats-doubt.md @@ -0,0 +1,7 @@ +--- +"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch +"vscode-ui5-language-assistant": patch +"@ui5-language-assistant/binding": patch +--- + +fix: do not show vSorterInfo as completion item and show correct diagnostic diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b455b2970..5011501ce 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,7 @@ jobs: - name: Upload vsix artifact if: matrix.os == 'ubuntu-latest' && matrix.node-version == '14.x' && github.event_name == 'push' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: vscode-extension-file path: ./packages/vscode-ui5-language-assistant/vscode-ui5-language-assistant*.vsix diff --git a/packages/binding/src/definition/definition.ts b/packages/binding/src/definition/definition.ts index c861b2be1..77ff80c65 100644 --- a/packages/binding/src/definition/definition.ts +++ b/packages/binding/src/definition/definition.ts @@ -1,388 +1,16 @@ +import { AGGREGATION_BINDING_INFO, PROPERTY_BINDING_INFO } from "../api"; +import { BindContext, BindingInfoElement, PropertyType } from "../types"; import { - UI5Class, - UI5Type, -} from "@ui5-language-assistant/semantic-model-types"; -import { - AGGREGATION_BINDING_INFO, - FILTER_OPERATOR, - PROPERTY_BINDING_INFO, -} from "../api"; -import { - BindContext, - BindingInfoElement, - BindingInfoName, - PropertyType, - TypeKind, - Dependents, - ClassName, -} from "../types"; -import { ui5NodeToFQN } from "@ui5-language-assistant/logic-utils"; -import { forOwn } from "lodash"; -import { getAltTypesPrime, getDocumentation } from "../utils"; + buildType, + findPrimitiveTypeInAggregation, + getDocumentation, +} from "../utils"; import { getFallBackElements } from "./fall-back-definition"; -import { getSorterPossibleElement } from "./sorter"; -import { getFiltersPossibleElement } from "./filter"; import type { UI5Aggregation, UI5TypedefProp, } from "@ui5-language-assistant/semantic-model-types"; -const notAllowedElements: Map = new Map([ - [BindingInfoName.path, [BindingInfoName.parts, BindingInfoName.value]], - [BindingInfoName.value, [BindingInfoName.parts, BindingInfoName.path]], - [BindingInfoName.parts, [BindingInfoName.value, BindingInfoName.path]], -]); -const dependents: Map = new Map([ - [ - BindingInfoName.constraints, - [ - { - name: BindingInfoName.type, - type: [ - { - kind: TypeKind.string, - dependents: [], - notAllowedElements: [], - }, - ], - }, - ], - ], - [ - BindingInfoName.formatOptions, - [ - { - name: BindingInfoName.type, - type: [ - { - kind: TypeKind.string, - dependents: [], - notAllowedElements: [], - }, - ], - }, - ], - ], -]); -const defaultBoolean: Map = new Map([ - ["Boolean", [true, false]], - ["boolean", [true, false]], -]); -const classKind: Map = new Map([ - [ClassName.Sorter, TypeKind.object], - [ClassName.Filter, TypeKind.object], -]); - -/** - * Retrieves class kind based on the provided class name. - * If the class name is not found in the classKind map, it falls back to TypeKind.string. - */ - -const getClassKind = (name: string) => - classKind.get(name as ClassName) ?? TypeKind.string; - -const getReference = (type: UI5Type) => { - let reference; - switch (type.kind) { - case "PrimitiveType": - case "UI5Enum": - case "UI5Class": - case "UI5Typedef": - if (type.name === ClassName.Filter) { - reference = "filters"; - } - break; - case "ArrayType": - if (type.type) { - reference = getReference(type.type); - } - break; - case "UnionType": - for (const t of type.types) { - reference = getReference(t); - if (reference) { - break; - } - } - break; - default: - break; - } - return reference; -}; - -/** - * Currently [api.json](https://ui5.sap.com/1.118.1/test-resources/sap/ui/core/designtime/api.json) provides these constructor parameters as old school convention - * e.g `sPath` for `path` where `s` stands for string type. These params are [map in runtime](https://github.com/SAP/openui5/blob/master/src/sap.ui.core/src/sap/ui/model/Sorter.js#L54-L60). - * We build a map to overcome old school convention - */ -const sorterMap = new Map([ - ["sPath", "path"], - ["path", "path"], - ["bDescending", "descending"], - ["descending", "descending"], - ["vGroup", "group"], - ["group", "group"], - ["fnComparator", "comparator"], - ["comparator", "comparator"], -]); -const getPossibleElement = (param: { - context: BindContext; - ui5Aggregation?: UI5Aggregation; - forHover?: boolean; - type: UI5Class; -}): BindingInfoElement[] => { - const result: BindingInfoElement[] = []; - /* istanbul ignore next */ - const { ui5Aggregation, forHover = false, type, context } = param; - if (type.name === ClassName.Sorter) { - const parameters = type.ctor && type.ctor.parameters; - if (!parameters) { - return getSorterPossibleElement(); - } - for (const constParam of parameters) { - if (!constParam.type) { - continue; - } - const reference = getReference(constParam.type); - const name = sorterMap.get(constParam.name) ?? constParam.name; - const data: BindingInfoElement = { - name, - // eslint-disable-next-line @typescript-eslint/no-use-before-define - type: buildType({ - context, - type: constParam.type, - name, - collection: false, - ui5Aggregation, - forHover, - reference, - }), - documentation: getDocumentation({ - context, - prop: constParam, - FQN: ui5NodeToFQN(type), - titlePrefix: "(class)", - forHover, - }), - }; - data.required = !constParam.optional; - result.push(data); - } - } - - if (type.name === ClassName.Filter) { - // for filters only try `vFilterInfo` from constructor - /* istanbul ignore next */ - const vFilter = type.ctor?.parameters.find((i) => i.name === "vFilterInfo"); - if (!vFilter) { - // use fallback filter - return getFiltersPossibleElement(); - } - for (const param of vFilter.parameterProperties) { - if (!param.type) { - continue; - } - // add reference to type and avoid recursion - const paramType = param.type; - const reference = getReference(paramType); - const data: BindingInfoElement = { - name: param.name, - // eslint-disable-next-line @typescript-eslint/no-use-before-define - type: buildType({ - context, - type: paramType, - name: param.name, - collection: false, - ui5Aggregation, - forHover, - reference, - }), - documentation: getDocumentation({ - context, - prop: param, - FQN: ui5NodeToFQN(type), - titlePrefix: "(class)", - forHover, - }), - }; - data.required = !param.optional; - result.push(data); - } - } - return result; -}; - -const getPossibleValuesForClass = ( - context: BindContext, - type: UI5Class -): string[] => { - const result: string[] = []; - forOwn(context.ui5Model.classes, (value, key) => { - let clzExtends = value.extends; - while (clzExtends !== undefined) { - if (clzExtends.name === type.name && clzExtends.kind === type.kind) { - result.push(key); - break; - } - clzExtends = clzExtends.extends; - } - }); - - return result; -}; - -const getFromMap = ( - map: Map, - name: U, - /* istanbul ignore next */ - aggregation = false -): T[] => { - return aggregation ? [] : map.get(name) ?? []; -}; - -const buildType = (param: { - context: BindContext; - type: UI5Type; - name: string; - collection?: boolean; - ui5Aggregation?: UI5Aggregation; - forHover?: boolean; - reference?: string; -}): PropertyType[] => { - /* istanbul ignore next */ - const { - collection = false, - ui5Aggregation, - forHover = false, - context, - type, - name, - reference, - } = param; - const aggregation = !!ui5Aggregation; - const propertyType: PropertyType[] = []; - switch (type.kind) { - case "UI5Any": { - propertyType.push({ - kind: TypeKind[type.name], - dependents: getFromMap(dependents, name, aggregation), - notAllowedElements: getFromMap(notAllowedElements, name, aggregation), - collection, - reference, - }); - break; - } - case "PrimitiveType": - propertyType.push({ - kind: TypeKind[type.name], - dependents: getFromMap(dependents, name, aggregation), - notAllowedElements: getFromMap(notAllowedElements, name, aggregation), - possibleValue: { - fixed: !!defaultBoolean.get(type.name), - values: getFromMap(defaultBoolean, type.name, aggregation), - }, - collection, - reference, - }); - break; - case "UI5Enum": - propertyType.push({ - kind: TypeKind.string, - dependents: getFromMap(dependents, name, aggregation), - notAllowedElements: getFromMap(notAllowedElements, name, aggregation), - possibleValue: { - fixed: true, - values: type.fields.map((field) => { - /** - * filter operator accepts `BT` as value in XML instead of FQN `sap.ui.model.FilterOperator.BT` - */ - if (type.name === FILTER_OPERATOR) { - return field.name; - } - return ui5NodeToFQN(field); - }), - }, - collection, - reference, - }); - break; - case "UI5Class": - propertyType.push({ - kind: getClassKind(type.name), - dependents: getFromMap(dependents, name, aggregation), - notAllowedElements: getFromMap(notAllowedElements, name, aggregation), - possibleElements: reference - ? [] - : getPossibleElement({ context, ui5Aggregation, forHover, type }), - possibleValue: { - fixed: false, - values: getPossibleValuesForClass(context, type), - }, - collection, - reference, - }); - break; - case "UI5Typedef": - if (TypeKind[type.name]) { - propertyType.push({ - kind: TypeKind[type.name], - dependents: getFromMap(dependents, name, aggregation), - notAllowedElements: getFromMap(notAllowedElements, name, aggregation), - collection, - reference, - }); - } - break; - case "UnionType": - for (const unionType of type.types) { - if (unionType.kind === "ArrayType" && unionType.type) { - propertyType.push( - ...buildType({ - context, - type: unionType.type, - name, - collection: true, - ui5Aggregation, - forHover, - reference, - }) - ); - } else { - propertyType.push( - ...buildType({ - context, - type: unionType, - name, - collection, - ui5Aggregation, - forHover, - reference, - }) - ); - } - } - break; - case "ArrayType": - if (!type.type) { - break; - } - propertyType.push( - ...buildType({ - context, - type: type.type, - name, - collection: true, - ui5Aggregation, - forHover, - reference, - }) - ); - break; - } - return propertyType; -}; - const removeDuplicate = (builtType: PropertyType[]): PropertyType[] => { const result = builtType.reduce( (previous: PropertyType[], current: PropertyType) => { @@ -449,6 +77,7 @@ const processUI5TypedefProperties = (param: { } return elements; }; + export const getBindingElements = ( context: BindContext, /* istanbul ignore next */ @@ -472,7 +101,7 @@ export const getBindingElements = ( properties: propBinding.properties, }) ); - const altTypes = getAltTypesPrime(aggregation); + const altTypes = findPrimitiveTypeInAggregation(aggregation); if (altTypes) { // if `altTypes`, add `PROPERTY_BINDING_INFO` properties too elements.push( diff --git a/packages/binding/src/services/completion/index.ts b/packages/binding/src/services/completion/index.ts index c646ce33d..ef5071b95 100644 --- a/packages/binding/src/services/completion/index.ts +++ b/packages/binding/src/services/completion/index.ts @@ -39,14 +39,10 @@ export function getCompletionItems(opts: { attributeValue: attributeValueProviders, }, }); - const uniqueSuggestion = [ - ...new Set(suggestions.map((i) => JSON.stringify(i))), - ].map((i) => JSON.parse(i)); - getLogger().trace("computed completion items", { - uniqueSuggestion, + suggestions, }); - return uniqueSuggestion; + return suggestions; } catch (error) { getLogger().debug("getCompletionItems failed:", error); return []; diff --git a/packages/binding/src/services/completion/providers/binding.ts b/packages/binding/src/services/completion/providers/binding.ts index 2db2ddffd..49c215869 100644 --- a/packages/binding/src/services/completion/providers/binding.ts +++ b/packages/binding/src/services/completion/providers/binding.ts @@ -17,6 +17,7 @@ import { import { BindContext } from "../../../types"; import { createInitialSnippet } from "./create-initial-snippet"; import { + findPrimitiveTypeInAggregation, getCursorContext, getLogger, isMacrosMetaContextPath, @@ -138,7 +139,17 @@ export function bindingSuggestions({ ) ); } - return completionItems; + + const altTypes = findPrimitiveTypeInAggregation(ui5Aggregation); + if (altTypes) { + // for `altTypes`, `PROPERTY_BINDING_INFO` properties are added (duplicate allowed) + return completionItems; + } + // Remove duplicates + const uniqueCompletionItems = Array.from( + new Map(completionItems.map((item) => [item.label, item])).values() + ); + return uniqueCompletionItems; } catch (error) { getLogger().debug("bindingSuggestions failed:", error); return []; diff --git a/packages/binding/src/services/diagnostics/validators/check-required.ts b/packages/binding/src/services/diagnostics/validators/check-required.ts index 95d143bfb..38582f583 100644 --- a/packages/binding/src/services/diagnostics/validators/check-required.ts +++ b/packages/binding/src/services/diagnostics/validators/check-required.ts @@ -1,4 +1,4 @@ -import { findRange, getAltTypesPrime } from "../../../utils"; +import { findRange, findPrimitiveTypeInAggregation } from "../../../utils"; import { BindingIssue, BINDING_ISSUE_TYPE, @@ -21,7 +21,7 @@ export const checkRequiredElement = ( /* istanbul ignore next */ (i) => i.key?.text === reqEl.name ); - const altTypes = getAltTypesPrime(aggregation); + const altTypes = findPrimitiveTypeInAggregation(aggregation); if (!usedRequiredEl && altTypes) { // some property e.g `tooltip` can be used with both `aggregation binding info` or `property binding info`. Therefore `altTypes` is defined in design time. // if `altTypes` is present, check if any element is used. This is a very broad check to avoid false diagnostic. diff --git a/packages/binding/src/utils/definition.ts b/packages/binding/src/utils/definition.ts new file mode 100644 index 000000000..63af2f41a --- /dev/null +++ b/packages/binding/src/utils/definition.ts @@ -0,0 +1,478 @@ +import { + UI5Aggregation, + UI5Class, + UI5ConstructorParameters, + UI5Type, +} from "@ui5-language-assistant/semantic-model-types"; +import { + BindContext, + BindingInfoElement, + BindingInfoName, + ClassName, + Dependents, + PropertyType, + TypeKind, +} from "../types"; +import { FILTER_OPERATOR } from "../constant"; +import { ui5NodeToFQN } from "@ui5-language-assistant/logic-utils"; +import { getSorterPossibleElement } from "../definition/sorter"; +import { getFiltersPossibleElement } from "../definition/filter"; +import { getDocumentation } from "./documentation"; +import { forOwn } from "lodash"; + +const notAllowedElements: Map = new Map([ + [BindingInfoName.path, [BindingInfoName.parts, BindingInfoName.value]], + [BindingInfoName.value, [BindingInfoName.parts, BindingInfoName.path]], + [BindingInfoName.parts, [BindingInfoName.value, BindingInfoName.path]], +]); +const dependents: Map = new Map([ + [ + BindingInfoName.constraints, + [ + { + name: BindingInfoName.type, + type: [ + { + kind: TypeKind.string, + dependents: [], + notAllowedElements: [], + }, + ], + }, + ], + ], + [ + BindingInfoName.formatOptions, + [ + { + name: BindingInfoName.type, + type: [ + { + kind: TypeKind.string, + dependents: [], + notAllowedElements: [], + }, + ], + }, + ], + ], +]); +const defaultBoolean: Map = new Map([ + ["Boolean", [true, false]], + ["boolean", [true, false]], +]); + +const classKind: Map = new Map([ + [ClassName.Sorter, TypeKind.object], + [ClassName.Filter, TypeKind.object], +]); + +/** + * Retrieves possible values from UI5 class which is extended. + * + * @param {BindContext} context - The binding context containing the UI5 model. + * @param {UI5Class} type - The UI5 class type to find possible values for. + * @returns {string[]} An array of possible values for the specified UI5 class. + */ +export const getPossibleValuesForClass = ( + context: BindContext, + type: UI5Class +): string[] => { + const result: string[] = []; + forOwn(context.ui5Model.classes, (value, key) => { + let clzExtends = value.extends; + while (clzExtends !== undefined) { + if (clzExtends.name === type.name && clzExtends.kind === type.kind) { + result.push(key); + break; + } + clzExtends = clzExtends.extends; + } + }); + + return result; +}; + +/** + * Currently [api.json](https://ui5.sap.com/1.118.1/test-resources/sap/ui/core/designtime/api.json) provides these constructor parameters as old school convention + * e.g `sPath` for `path` where `s` stands for string type. These params are [map in runtime](https://github.com/SAP/openui5/blob/master/src/sap.ui.core/src/sap/ui/model/Sorter.js#L54-L60). + * We build a map to overcome old school convention + */ +const sorterMap = new Map([ + ["sPath", "path"], + ["path", "path"], + ["bDescending", "descending"], + ["descending", "descending"], + ["vGroup", "group"], + ["group", "group"], + ["fnComparator", "comparator"], + ["comparator", "comparator"], +]); + +const getSorterElements = (param: { + context: BindContext; + ui5Aggregation?: UI5Aggregation; + forHover?: boolean; + type: UI5Class; +}): BindingInfoElement[] => { + const { ui5Aggregation, forHover = false, type, context } = param; + const result: BindingInfoElement[] = []; + const parameters = type.ctor?.parameters; + if (!parameters) { + return getSorterPossibleElement(); + } + for (const constParam of parameters) { + if (!constParam.type) { + continue; + } + const name = sorterMap.get(constParam.name) ?? constParam.name; + if (name === "vSorterInfo" && constParam.parameterProperties) { + result.push( + ...getConstructorParameterProperties({ + ...param, + parameterProperties: constParam.parameterProperties, + }) + ); + continue; + } + const reference = getReference(constParam.type); + const data: BindingInfoElement = { + name, + // eslint-disable-next-line @typescript-eslint/no-use-before-define + type: buildType({ + context, + type: constParam.type, + name, + collection: false, + ui5Aggregation, + forHover, + reference, + }), + documentation: getDocumentation({ + context, + prop: constParam, + FQN: ui5NodeToFQN(type), + titlePrefix: "(class)", + forHover, + }), + }; + data.required = !constParam.optional; + result.push(data); + } + return result; +}; + +const getFilterElements = (param: { + context: BindContext; + ui5Aggregation?: UI5Aggregation; + forHover?: boolean; + type: UI5Class; +}): BindingInfoElement[] => { + const { type } = param; + // for filters only try `vFilterInfo` from constructor + /* istanbul ignore next */ + const vFilter = type.ctor?.parameters?.find((i) => i.name === "vFilterInfo"); + if (!vFilter) { + // use fallback filter + return getFiltersPossibleElement(); + } + return getConstructorParameterProperties({ + ...param, + parameterProperties: vFilter.parameterProperties, + }); +}; +/** + * Retrieves possible binding information elements for a given UI5 class. + * + * @param {Object} param - The parameters for retrieving possible elements. + * @param {BindContext} param.context - The binding context containing the UI5 model. + * @param {UI5Aggregation} [param.ui5Aggregation] - The UI5 aggregation information. + * @param {boolean} [param.forHover=false] - Flag indicating if the elements are for hover information. + * @param {UI5Class} param.type - The UI5 class type to find possible elements for. + * @returns {BindingInfoElement[]} An array of possible binding information elements for the specified UI5 class. + */ +export const getPossibleElement = (param: { + context: BindContext; + ui5Aggregation?: UI5Aggregation; + forHover?: boolean; + type: UI5Class; +}): BindingInfoElement[] => { + const result: BindingInfoElement[] = []; + /* istanbul ignore next */ + const { type } = param; + if (type.name === ClassName.Sorter) { + return getSorterElements(param); + } + + if (type.name === ClassName.Filter) { + return getFilterElements(param); + } + return result; +}; + +/** + * Retrieves class kind based on the provided class name. + * If the class name is not found in the classKind map, it falls back to TypeKind.string. + */ +const getClassKind = (name: string) => + classKind.get(name as ClassName) ?? TypeKind.string; + +/** + * Retrieves a reference to other element based on the provided UI5Type. Currently only for `filters`. + * + * @param {UI5Type} type - The type object to get the reference for. + * @returns {string | undefined} The reference string if found, otherwise undefined. + */ +export function getReference(type: UI5Type): string | undefined { + let reference; + switch (type.kind) { + case "PrimitiveType": + case "UI5Enum": + case "UI5Class": + case "UI5Typedef": + if (type.name === ClassName.Filter) { + reference = "filters"; + } + break; + case "ArrayType": + if (type.type) { + reference = getReference(type.type); + } + break; + case "UnionType": + for (const t of type.types) { + reference = getReference(t); + if (reference) { + break; + } + } + break; + default: + break; + } + return reference; +} + +const getFromMap = ( + map: Map, + name: U, + /* istanbul ignore next */ + aggregation = false +): T[] => { + return aggregation ? [] : map.get(name) ?? []; +}; + +/** + * Builds a PropertyType array based on the provided parameters. + * + * @param {Object} param - The parameters for building the PropertyType array. + * @param {BindContext} param.context - The binding context. + * @param {UI5Type} param.type - The UI5 type. + * @param {string} param.name - The name of the property. + * @param {boolean} [param.collection=false] - Indicates if the property is a collection. + * @param {UI5Aggregation} [param.ui5Aggregation] - The UI5 aggregation. + * @param {boolean} [param.forHover=false] - Indicates if the property is for hover. + * @param {string} [param.reference] - The reference string. + * @returns {PropertyType[]} The built PropertyType array. + */ +export const buildType = (param: { + context: BindContext; + type: UI5Type; + name: string; + collection?: boolean; + ui5Aggregation?: UI5Aggregation; + forHover?: boolean; + reference?: string; +}): PropertyType[] => { + /* istanbul ignore next */ + const { + collection = false, + ui5Aggregation, + forHover = false, + context, + type, + name, + reference, + } = param; + const aggregation = !!ui5Aggregation; + const propertyType: PropertyType[] = []; + switch (type.kind) { + case "UI5Any": { + propertyType.push({ + kind: TypeKind[type.name], + dependents: getFromMap(dependents, name, aggregation), + notAllowedElements: getFromMap(notAllowedElements, name, aggregation), + collection, + reference, + }); + break; + } + case "PrimitiveType": + propertyType.push({ + kind: TypeKind[type.name], + dependents: getFromMap(dependents, name, aggregation), + notAllowedElements: getFromMap(notAllowedElements, name, aggregation), + possibleValue: { + fixed: !!defaultBoolean.get(type.name), + values: getFromMap(defaultBoolean, type.name, aggregation), + }, + collection, + reference, + }); + break; + case "UI5Enum": + propertyType.push({ + kind: TypeKind.string, + dependents: getFromMap(dependents, name, aggregation), + notAllowedElements: getFromMap(notAllowedElements, name, aggregation), + possibleValue: { + fixed: true, + values: type.fields.map((field) => { + /** + * filter operator accepts `BT` as value in XML instead of FQN `sap.ui.model.FilterOperator.BT` + */ + if (type.name === FILTER_OPERATOR) { + return field.name; + } + return ui5NodeToFQN(field); + }), + }, + collection, + reference, + }); + break; + case "UI5Class": + propertyType.push({ + kind: getClassKind(type.name), + dependents: getFromMap(dependents, name, aggregation), + notAllowedElements: getFromMap(notAllowedElements, name, aggregation), + possibleElements: reference + ? [] + : getPossibleElement({ context, ui5Aggregation, forHover, type }), + possibleValue: { + fixed: false, + values: getPossibleValuesForClass(context, type), + }, + collection, + reference, + }); + break; + case "UI5Typedef": + if (TypeKind[type.name]) { + propertyType.push({ + kind: TypeKind[type.name], + dependents: getFromMap(dependents, name, aggregation), + notAllowedElements: getFromMap(notAllowedElements, name, aggregation), + collection, + reference, + }); + } + break; + case "UnionType": + for (const unionType of type.types) { + if (unionType.kind === "ArrayType" && unionType.type) { + propertyType.push( + ...buildType({ + context, + type: unionType.type, + name, + collection: true, + ui5Aggregation, + forHover, + reference, + }) + ); + } else { + propertyType.push( + ...buildType({ + context, + type: unionType, + name, + collection, + ui5Aggregation, + forHover, + reference, + }) + ); + } + } + break; + case "ArrayType": + if (!type.type) { + break; + } + propertyType.push( + ...buildType({ + context, + type: type.type, + name, + collection: true, + ui5Aggregation, + forHover, + reference, + }) + ); + break; + } + return propertyType; +}; + +/** + * Retrieves the constructor parameter properties. + * + * @param {Object} param - The parameter object. + * @param {BindContext} param.context - The binding context. + * @param {UI5ConstructorParameters[]} param.parameterProperties - The UI5 constructor parameters. + * @param {UI5Aggregation} [param.ui5Aggregation] - The UI5 aggregation. + * @param {boolean} [param.forHover=false] - Indicates if the properties are for hover. + * @param {UI5Class} param.type - The UI5 class type. + * @returns {BindingInfoElement[]} The array of binding information elements. + */ +export function getConstructorParameterProperties(param: { + context: BindContext; + parameterProperties: UI5ConstructorParameters[]; + ui5Aggregation?: UI5Aggregation; + forHover?: boolean; + type: UI5Class; +}): BindingInfoElement[] { + const result: BindingInfoElement[] = []; + const { + ui5Aggregation, + forHover = false, + type, + context, + parameterProperties, + } = param; + for (const param of parameterProperties) { + if (!param.type) { + continue; + } + // add reference to type and avoid recursion + const paramType = param.type; + const reference = getReference(paramType); + const data: BindingInfoElement = { + name: param.name, + // eslint-disable-next-line @typescript-eslint/no-use-before-define + type: buildType({ + context, + type: paramType, + name: param.name, + collection: false, + ui5Aggregation, + forHover, + reference, + }), + documentation: getDocumentation({ + context, + prop: param, + FQN: ui5NodeToFQN(type), + titlePrefix: "(class)", + forHover, + }), + }; + data.required = !param.optional; + result.push(data); + } + + return result; +} diff --git a/packages/binding/src/utils/index.ts b/packages/binding/src/utils/index.ts index 6009d302a..b2b30a6dc 100644 --- a/packages/binding/src/utils/index.ts +++ b/packages/binding/src/utils/index.ts @@ -20,7 +20,22 @@ export { getLogger } from "./logger"; export { getDocumentation } from "./documentation"; -export const getAltTypesPrime = ( +/** + * Finds and returns the first alternative type in the aggregation's altTypes array + * that has a kind property equal to "PrimitiveType". + * + * @param {UI5Aggregation} [aggregation] - The aggregation object which may contain alternative types. + * @returns {UI5Type | undefined} - The first alternative type with kind "PrimitiveType", or undefined if not found. + */ +export const findPrimitiveTypeInAggregation = ( aggregation?: UI5Aggregation ): UI5Type | undefined => aggregation?.altTypes?.find((i) => i.kind === "PrimitiveType"); + +export { + getReference, + buildType, + getPossibleValuesForClass, + getConstructorParameterProperties, + getPossibleElement, +} from "./definition"; diff --git a/packages/binding/test/unit/services/completion/aggregation-binding.test.ts b/packages/binding/test/unit/services/completion/aggregation-binding.test.ts index 28937a0e9..17651ef40 100644 --- a/packages/binding/test/unit/services/completion/aggregation-binding.test.ts +++ b/packages/binding/test/unit/services/completion/aggregation-binding.test.ts @@ -14,6 +14,7 @@ import { ViewCompletionProviderType, } from "../../helper"; import { initI18n } from "../../../../src/api"; +import { readFile, writeFile } from "fs/promises"; describe("aggregation binding", () => { let getCompletionResult: ViewCompletionProviderType; @@ -105,6 +106,24 @@ describe("aggregation binding", () => { const result = await getCompletionResult(snippet); expect(result).toMatchSnapshot(); }); + + it("do not show `vSorterInfo`. Some UI5 version e.g. 1.131.0 has `vSorterInfo` as param of constructor ", async function () { + // adapt manifest.json file + const manifestPath = join( + root, + "app", + "manage_travels", + "webapp", + "manifest.json" + ); + const manifest = JSON.parse(await readFile(manifestPath, "utf-8")); + manifest["sap.ui5"]["dependencies"]["minUI5Version"] = "1.131.0"; + await writeFile(manifestPath, JSON.stringify(manifest, null, 2)); + const snippet = ` + `; + const result = await getCompletionResult(snippet); + expect(result.find((i) => i.label === "vSorterInfo")).toBeUndefined(); + }); }); describe("filters", function () { it("initial", async function () { diff --git a/packages/binding/test/unit/utils/__snapshots__/definition.test.ts.snap b/packages/binding/test/unit/utils/__snapshots__/definition.test.ts.snap new file mode 100644 index 000000000..479d32bb4 --- /dev/null +++ b/packages/binding/test/unit/utils/__snapshots__/definition.test.ts.snap @@ -0,0 +1,348 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getPossibleElement should return constructor parameter properties for filter when name is vFilterInfo 1`] = ` +Array [ + Object { + "documentation": Object { + "kind": "markdown", + "value": "\`(class) Filter\` + + + +**undefined** Boolean + +**undefined** undefined + +**undefined** undefined + +undefined(https://sdk.openui5.org/#/api/Filter)", + }, + "name": "value1", + "required": true, + "type": Array [ + Object { + "collection": false, + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + "possibleValue": Object { + "fixed": true, + "values": Array [ + true, + false, + ], + }, + "reference": undefined, + }, + ], + }, +] +`; + +exports[`getPossibleElement should return constructor parameter properties for sorter when name is vSorterInfo 1`] = ` +Array [ + Object { + "documentation": Object { + "kind": "markdown", + "value": "\`(class) Sorter\` + + + +**undefined** Boolean + +**undefined** undefined + +**undefined** undefined + +undefined(https://sdk.openui5.org/#/api/Sorter)", + }, + "name": "param1", + "required": true, + "type": Array [ + Object { + "collection": false, + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + "possibleValue": Object { + "fixed": true, + "values": Array [ + true, + false, + ], + }, + "reference": undefined, + }, + ], + }, + Object { + "documentation": Object { + "kind": "markdown", + "value": "\`(class) Sorter\` + + + +**undefined** + +**undefined** undefined + +**undefined** undefined + +undefined(https://sdk.openui5.org/#/api/Sorter)", + }, + "name": "path", + "required": true, + "type": Array [], + }, +] +`; + +exports[`getPossibleElement should return filter possible elements when type is Filter and vFilterInfo is not defined - fallback 1`] = ` +Array [ + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "path", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "test", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "comparator", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "operator", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + "possibleValue": Object { + "fixed": true, + "values": Array [ + "All", + "Any", + "BT", + "Contains", + "EndsWith", + "EQ", + "GE", + "GT", + "LE", + "LT", + "NB", + "NE", + "NotContains", + "NotEndsWith", + "NotStartsWith", + "StartsWith", + ], + }, + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "value1", + "type": Array [ + Object { + "dependents": Array [], + "kind": "any", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "value2", + "type": Array [ + Object { + "dependents": Array [], + "kind": "any", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "variable", + "type": Array [ + Object { + "dependents": Array [], + "kind": "any", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "condition", + "type": Array [ + Object { + "dependents": Array [], + "kind": "object", + "notAllowedElements": Array [], + "reference": "filters", + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "filters", + "type": Array [ + Object { + "collection": true, + "dependents": Array [], + "kind": "object", + "notAllowedElements": Array [], + "reference": "filters", + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "and", + "type": Array [ + Object { + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "caseSensitive", + "type": Array [ + Object { + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + }, + ], + }, +] +`; + +exports[`getPossibleElement should return sorter possible elements when type is Sorter and parameters are not defined - fallback 1`] = ` +Array [ + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "path", + "required": true, + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "descending", + "type": Array [ + Object { + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "group", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + Object { + "dependents": Array [], + "kind": "boolean", + "notAllowedElements": Array [], + }, + ], + }, + Object { + "documentation": Object { + "kind": "plaintext", + "value": "", + }, + "name": "comparator", + "type": Array [ + Object { + "dependents": Array [], + "kind": "string", + "notAllowedElements": Array [], + }, + ], + }, +] +`; diff --git a/packages/binding/test/unit/utils/definition.test.ts b/packages/binding/test/unit/utils/definition.test.ts new file mode 100644 index 000000000..8b25f7a14 --- /dev/null +++ b/packages/binding/test/unit/utils/definition.test.ts @@ -0,0 +1,568 @@ +import { + UI5Class, + UI5ConstructorParameters, + UI5Type, +} from "@ui5-language-assistant/semantic-model-types"; +import { + getReference, + getPossibleValuesForClass, + buildType, + getConstructorParameterProperties, + getPossibleElement, +} from "../../../src/utils"; +import { BindContext } from "../../../src/types"; +import { OPEN_FRAMEWORK } from "@ui5-language-assistant/constant"; + +function createType(param: unknown): T { + return param as T; +} + +describe("getReference", () => { + it('should return "filters" for PrimitiveType with name Filter', () => { + const type = { kind: "PrimitiveType", name: "Filter" }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it('should return "filters" for UI5Enum with name Filter', () => { + const type = { kind: "UI5Enum", name: "Filter" }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it('should return "filters" for UI5Class with name Filter', () => { + const type = { kind: "UI5Class", name: "Filter" }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it('should return "filters" for UI5Typedef with name Filter', () => { + const type = { kind: "UI5Typedef", name: "Filter" }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it("should return undefined for PrimitiveType with different name", () => { + const type = { kind: "PrimitiveType", name: "Other" }; + expect(getReference(createType(type))).toBeUndefined(); + }); + + it("should return reference for ArrayType", () => { + const type = { + kind: "ArrayType", + type: { kind: "PrimitiveType", name: "Filter" }, + }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it("should return reference for UnionType", () => { + const type = { + kind: "UnionType", + types: [ + { kind: "PrimitiveType", name: "Other" }, + { kind: "PrimitiveType", name: "Filter" }, + ], + }; + expect(getReference(createType(type))).toBe("filters"); + }); + + it("should return undefined for unknown type", () => { + const type = { kind: "UnknownType", name: "Filter" }; + expect(getReference(createType(type))).toBeUndefined(); + }); +}); + +describe("getPossibleValuesForClass", () => { + it("should return possible values for a given UI5 class", () => { + const context = { + ui5Model: { + classes: { + ClassA: { extends: { name: "BaseClass", kind: "class" } }, + ClassB: { extends: { name: "BaseClass", kind: "class" } }, + ClassC: { extends: { name: "OtherClass", kind: "class" } }, + }, + }, + }; + + const type = { name: "BaseClass", kind: "class" }; + + const result = getPossibleValuesForClass( + createType(context), + createType(type) + ); + + expect(result).toEqual(["ClassA", "ClassB"]); + }); + + it("should return an empty array if no classes extend the given UI5 class", () => { + const context = { + ui5Model: { + classes: { + ClassA: { extends: { name: "OtherClass", kind: "class" } }, + ClassB: { extends: { name: "OtherClass", kind: "class" } }, + }, + }, + }; + + const type = { name: "BaseClass", kind: "class" }; + + const result = getPossibleValuesForClass( + createType(context), + createType(type) + ); + + expect(result).toEqual([]); + }); + + it("should handle nested extensions correctly", () => { + const context = { + ui5Model: { + classes: { + ClassA: { + extends: { + name: "IntermediateClass", + kind: "class", + extends: { name: "BaseClass", kind: "class" }, + }, + }, + ClassB: { extends: { name: "BaseClass", kind: "class" } }, + }, + }, + }; + + const type = { name: "BaseClass", kind: "class" }; + + const result = getPossibleValuesForClass( + createType(context), + createType(type) + ); + + expect(result).toEqual(["ClassA", "ClassB"]); + }); +}); + +describe("buildType", () => { + it("should build PropertyType array for UI5Any type", () => { + const param = { + context: createType({}), + type: createType({ kind: "UI5Any", name: "any" }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "any", + dependents: expect.any(Array), + notAllowedElements: expect.any(Array), + collection: false, + reference: undefined, + }, + ]); + }); + + it("should build PropertyType array for PrimitiveType", () => { + const param = { + context: createType({}), + type: createType({ kind: "PrimitiveType", name: "Boolean" }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "boolean", + dependents: expect.any(Array), + notAllowedElements: expect.any(Array), + possibleValue: { + fixed: expect.any(Boolean), + values: expect.any(Array), + }, + collection: false, + reference: undefined, + }, + ]); + }); + + it("should build PropertyType array for UI5Enum type", () => { + const param = { + context: createType({}), + type: createType({ + kind: "UI5Enum", + name: "EnumType", + fields: [{ name: "Field1" }], + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "string", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: ["Field1"], + }, + collection: false, + reference: undefined, + }, + ]); + }); + it("should build PropertyType array for UI5Enum type - [FilterOperator]", () => { + const param = { + context: createType({}), + type: createType({ + kind: "UI5Enum", + name: "FilterOperator", + fields: [{ name: "BT" }], + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "string", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: ["BT"], + }, + collection: false, + reference: undefined, + }, + ]); + }); + it("should build PropertyType array for UI5Typedef type", () => { + const param = { + context: createType({}), + type: createType({ + kind: "UI5Typedef", + name: "string", + properties: [], + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "string", + dependents: [], + notAllowedElements: [], + collection: false, + reference: undefined, + }, + ]); + }); + + it("should build PropertyType array for UI5Class type", () => { + const param = { + context: createType({ ui5Model: {} }), + type: createType({ kind: "UI5Class", name: "ClassType" }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: expect.any(String), + dependents: [], + notAllowedElements: [], + possibleElements: [], + possibleValue: { + fixed: false, + values: [], + }, + collection: false, + reference: undefined, + }, + ]); + }); + + it("should build PropertyType array for UnionType", () => { + const param = { + context: createType({}), + type: createType({ + kind: "UnionType", + types: [{ kind: "PrimitiveType", name: "Boolean" }], + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "boolean", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: [true, false], + }, + collection: false, + reference: undefined, + }, + ]); + }); + it("should build PropertyType array for UnionType - ArrayType", () => { + const param = { + context: createType({}), + type: createType({ + kind: "UnionType", + types: [ + { + kind: "ArrayType", + type: { kind: "PrimitiveType", name: "Boolean" }, + }, + ], + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "boolean", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: [true, false], + }, + collection: true, + reference: undefined, + }, + ]); + }); + + it("should build PropertyType array for ArrayType", () => { + const param = { + context: createType({}), + type: createType({ + kind: "ArrayType", + type: { kind: "PrimitiveType", name: "Boolean" }, + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([ + { + kind: "boolean", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: [true, false], + }, + collection: true, + reference: undefined, + }, + ]); + }); + it("should build PropertyType array for ArrayType - undefined type", () => { + const param = { + context: createType({}), + type: createType({ + kind: "ArrayType", + }), + name: "testName", + collection: false, + }; + const result = buildType(param); + expect(result).toEqual([]); + }); +}); + +describe("getConstructorParameterProperties", () => { + const mockContext = createType({ + ui5Model: { + framework: OPEN_FRAMEWORK, + }, + }); + + const mockUI5Class = createType({ + kind: "UI5Class", + abstract: false, + extends: undefined, + implements: [], + ctor: undefined, + methods: [], + properties: [], + fields: [], + aggregations: [], + associations: [], + events: [], + defaultAggregation: undefined, + returnTypes: [], + }); + + it("should return an empty array when parameterProperties is empty", () => { + const result = getConstructorParameterProperties({ + context: mockContext, + parameterProperties: [], + type: mockUI5Class, + }); + expect(result).toEqual([]); + }); + + it("should return an array of BindingInfoElement when parameterProperties contains valid data", () => { + const mockParameterProperties: UI5ConstructorParameters[] = [ + { + kind: "UI5TypedefProp", + name: "param1", + type: createType({ kind: "PrimitiveType", name: "Boolean" }), + parameterProperties: [], + }, + ]; + + const result = getConstructorParameterProperties({ + context: mockContext, + parameterProperties: mockParameterProperties, + type: mockUI5Class, + }); + + expect(result).toEqual([ + { + name: "param1", + type: [ + { + kind: "boolean", + dependents: [], + notAllowedElements: [], + possibleValue: { + fixed: true, + values: [true, false], + }, + collection: false, + reference: undefined, + }, + ], + documentation: { + kind: "markdown", + value: + "`(class) `\n\n\n\n**undefined** Boolean\n\n**undefined** undefined\n\n**undefined** undefined\n\nundefined(https://sdk.openui5.org/#/api/)", + }, + required: true, + }, + ]); + }); + + it("should skip parameterProperties with missing type", () => { + const mockParameterProperties: UI5ConstructorParameters[] = [ + { + kind: "UI5TypedefProp", + name: "param1", + type: undefined, + parameterProperties: [], + }, + ]; + + const result = getConstructorParameterProperties({ + context: mockContext, + parameterProperties: mockParameterProperties, + type: mockUI5Class, + }); + + expect(result).toEqual([]); + }); +}); + +describe("getPossibleElement", () => { + const context = createType({ + ui5Model: { + framework: OPEN_FRAMEWORK, + }, + }); + + it("should return sorter possible elements when type is Sorter and parameters are not defined - fallback", () => { + const type = createType({ + name: "Sorter", + kind: "UI5Class", + ctor: {}, + }); + const result = getPossibleElement({ context, type }); + + expect(result).toMatchSnapshot(); + }); + + it("should return constructor parameter properties for sorter when name is vSorterInfo", () => { + const type = createType({ + name: "Sorter", + kind: "UI5Class", + ctor: { + parameters: [ + { + name: "vSorterInfo", + type: "someType", + parameterProperties: [ + { + kind: "UI5TypedefProp", + name: "param1", + type: createType({ + kind: "PrimitiveType", + name: "Boolean", + }), + parameterProperties: [], + }, + ], + }, + { + name: "path", + type: "string", + parameterProperties: [], + }, + ], + }, + }); + const result = getPossibleElement({ context, type }); + + expect(result).toMatchSnapshot(); + }); + + it("should return filter possible elements when type is Filter and vFilterInfo is not defined - fallback", () => { + const type = createType({ + name: "Filter", + kind: "UI5Class", + ctor: {}, + }); + const result = getPossibleElement({ context, type }); + + expect(result).toMatchSnapshot(); + }); + + it("should return constructor parameter properties for filter when name is vFilterInfo", () => { + const type = createType({ + name: "Filter", + kind: "UI5Class", + ctor: { + parameters: [ + { + name: "vFilterInfo", + type: "someType", + parameterProperties: [ + { + kind: "UI5TypedefProp", + name: "value1", + type: createType({ + kind: "PrimitiveType", + name: "Boolean", + }), + parameterProperties: [], + }, + ], + }, + { + name: "path", + type: "string", + parameterProperties: [], + }, + ], + }, + }); + + const result = getPossibleElement({ context, type }); + + expect(result).toMatchSnapshot(); + }); +}); diff --git a/packages/constant/src/constant.ts b/packages/constant/src/constant.ts index 6e5a6f901..0bb3ced44 100644 --- a/packages/constant/src/constant.ts +++ b/packages/constant/src/constant.ts @@ -2,9 +2,9 @@ export const OPEN_FRAMEWORK = "OpenUI5"; export const DEFAULT_UI5_FRAMEWORK = "SAPUI5"; //https://ui5.sap.com/version.json -export const DEFAULT_UI5_VERSION = "1.71.72"; +export const DEFAULT_UI5_VERSION = "1.71.73"; // https://sdk.openui5.org/version.json -export const DEFAULT_OPEN_UI5_VERSION = "1.71.68"; +export const DEFAULT_OPEN_UI5_VERSION = "1.71.69"; export const DEFAULT_UI5_VERSION_BASE = "1.71"; diff --git a/packages/logic-utils/test/unit/ui5.test.ts b/packages/logic-utils/test/unit/ui5.test.ts index 2c3ba7c6b..59848ac95 100644 --- a/packages/logic-utils/test/unit/ui5.test.ts +++ b/packages/logic-utils/test/unit/ui5.test.ts @@ -72,7 +72,7 @@ describe("getVersionInfoUrl", () => { DEFAULT_UI5_VERSION ); expect(result).toStrictEqual( - "https://ui5.sap.com/1.71.72/resources/sap-ui-version.json" + `https://ui5.sap.com/${DEFAULT_UI5_VERSION}/resources/sap-ui-version.json` ); }); it("get version info uri for OpenUI5", async () => { @@ -81,7 +81,7 @@ describe("getVersionInfoUrl", () => { DEFAULT_OPEN_UI5_VERSION ); expect(result).toStrictEqual( - "https://sdk.openui5.org/1.71.68/resources/sap-ui-version.json" + `https://sdk.openui5.org/${DEFAULT_OPEN_UI5_VERSION}/resources/sap-ui-version.json` ); }); }); @@ -93,7 +93,7 @@ it("getLibraryAPIJsonUrl", async () => { "sap.m" ); expect(result).toStrictEqual( - "https://ui5.sap.com/1.71.72/test-resources/sap/m/designtime/api.json" + `https://ui5.sap.com/${DEFAULT_UI5_VERSION}/test-resources/sap/m/designtime/api.json` ); }); @@ -101,7 +101,7 @@ describe("getVersionsMap", () => { it("get version map for SAPUI5 - http success", async () => { const data = { "1.71": { - version: "1.71.70", + version: DEFAULT_UI5_VERSION, support: "Maintenance", lts: true, }, @@ -117,7 +117,7 @@ describe("getVersionsMap", () => { it("get version map for OpenUI5 - fallback default", async () => { const data = { latest: { - version: "1.71.68", + version: DEFAULT_OPEN_UI5_VERSION, support: "Maintenance", lts: true, }, diff --git a/packages/semantic-model/src/convert.ts b/packages/semantic-model/src/convert.ts index c3e81afe6..1100575fe 100644 --- a/packages/semantic-model/src/convert.ts +++ b/packages/semantic-model/src/convert.ts @@ -51,8 +51,7 @@ export function convertToSemanticModel( const libSemanticModel = convertLibraryToSemanticModel( libraryName, fileContent, - jsonSymbols, - strict + jsonSymbols ); addLibraryToModel(libSemanticModel, model); } else if (strict) { @@ -76,8 +75,7 @@ function addLibraryToModel( function convertLibraryToSemanticModel( libName: string, lib: apiJson.SchemaForApiJsonFiles, - jsonSymbols: Record, - strict: boolean + jsonSymbols: Record ): model.UI5SemanticModel { const model: model.UI5SemanticModel = { version: lib.version, @@ -105,7 +103,7 @@ function convertLibraryToSemanticModel( if (has(jsonSymbols, fqn)) { error( `${libName}: Duplicate symbol found: ${symbol.kind} ${fqn}. First occurrence is a ${jsonSymbols[fqn].kind}.`, - strict + false ); continue; } diff --git a/packages/semantic-model/test/unit/api-negative.test.ts b/packages/semantic-model/test/unit/api-negative.test.ts index 8052411f3..f10991109 100644 --- a/packages/semantic-model/test/unit/api-negative.test.ts +++ b/packages/semantic-model/test/unit/api-negative.test.ts @@ -612,7 +612,6 @@ describe("The ui5-language-assistant semantic model package API negative tests", }); }); describe("duplicate symbol", () => { - const message = "Duplicate symbol found"; describe("of the same kind", () => { const fileContent = { "$schema-ref": @@ -644,10 +643,6 @@ describe("The ui5-language-assistant semantic model package API negative tests", ], }; - it("fails in strict mode", () => { - assertGenerateThrowsInStrictMode(fileContent, message); - }); - it("doesn't fail and adds the first symbol in non-strict mode", () => { const model = assertGenerateDoesntThrowInNonStrictMode(fileContent); assertGeneratedModel(model, false); @@ -688,10 +683,6 @@ describe("The ui5-language-assistant semantic model package API negative tests", ], }; - it("fails in strict mode", () => { - assertGenerateThrowsInStrictMode(fileContent, message); - }); - it("doesn't fail and adds the first symbol in non-strict mode", () => { const model = assertGenerateDoesntThrowInNonStrictMode(fileContent); assertGeneratedModel(model, false); diff --git a/packages/semantic-model/test/unit/api.test.ts b/packages/semantic-model/test/unit/api.test.ts index 88c812e65..a2ef4bf62 100644 --- a/packages/semantic-model/test/unit/api.test.ts +++ b/packages/semantic-model/test/unit/api.test.ts @@ -312,7 +312,7 @@ describe("The ui5-language-assistant semantic model package API", () => { // TODO: old patches may be removed, should be updated continuously const versions: TestModelVersion[] = [ DEFAULT_UI5_VERSION, - "1.84.41", + "1.84.51", "1.96.27", // "1.105.0", ]; diff --git a/test-packages/test-utils/api.d.ts b/test-packages/test-utils/api.d.ts index 583bcc63c..a1fd5da78 100644 --- a/test-packages/test-utils/api.d.ts +++ b/test-packages/test-utils/api.d.ts @@ -92,7 +92,7 @@ export function buildUI5Model>( // TODO: list should be updated continuously! export type TestModelVersion = | /* OOM */ typeof DEFAULT_UI5_VERSION - | "1.84.41" + | "1.84.51" | "1.96.27" | "1.108.26" | "1.114.11"; diff --git a/test-packages/test-utils/src/utils/download-ui5-resources.ts b/test-packages/test-utils/src/utils/download-ui5-resources.ts index 1f7a7e1d5..48dec6c26 100644 --- a/test-packages/test-utils/src/utils/download-ui5-resources.ts +++ b/test-packages/test-utils/src/utils/download-ui5-resources.ts @@ -54,14 +54,7 @@ async function fetch(url: RequestInfo, init?: RequestInit): Promise { } async function getLibs(version: TestModelVersion): Promise { - // The metadata.json seems to have been added only very recently :( - // For now we assume the libraries are the same in all versions, when we support newer versions we should - // do a better check here - let versionInMetadataURL: string = version; - if (versionInMetadataURL !== "1.71.60") { - versionInMetadataURL = "1.71.60"; - } - const url = `https://ui5.sap.com/${versionInMetadataURL}/resources/sap-ui-version.json`; + const url = `https://ui5.sap.com/${version}/resources/sap-ui-version.json`; const response = await fetch(url); if (!response.ok) { log(`error fetching from ${url}`); diff --git a/test-packages/test-utils/src/utils/semantic-model-provider.ts b/test-packages/test-utils/src/utils/semantic-model-provider.ts index 4e041919d..101d06a85 100644 --- a/test-packages/test-utils/src/utils/semantic-model-provider.ts +++ b/test-packages/test-utils/src/utils/semantic-model-provider.ts @@ -14,7 +14,7 @@ const MODEL_CACHE: Record = Object.create(null); const fixes: Record = { - "1.71.72": { + "1.71.73": { array: "any[]", Array: "any[]", bloolean: undefined, @@ -47,7 +47,7 @@ const fixes: Record = { "sap.viz.ui5.controls.VizRangeSlider": undefined, any: "any", }, - "1.84.41": { + "1.84.51": { array: "any[]", Array: "any[]", Control: "sap.ui.core.Control", @@ -258,8 +258,8 @@ type LibraryFix = (content: Json) => void; // Library version -> library name -> fix function const libraryFixes: Record> = { - "1.71.72": {}, - "1.84.41": {}, + "1.71.73": {}, + "1.84.51": {}, "1.96.27": { "sap.ui.mdc": [ (content: Json): void => {