UNPKG

@ui5/linter

Version:

A static code analysis tool for UI5

191 lines 9.26 kB
import ManifestReporter from "./ManifestReporter.js"; import { deprecatedLibraries, deprecatedComponents } from "../../utils/deprecations.js"; import { MESSAGE } from "../messages.js"; import semver from "semver"; import { parseManifest } from "./parser.js"; import RemoveJsonPropertyFix from "./fix/RemoveJsonPropertyFix.js"; const deprecatedViewTypes = ["JSON", "HTML", "JS", "Template"]; export default class ManifestLinter { #reporter; #content; #resourcePath; #context; constructor(resourcePath, content, context) { this.#resourcePath = resourcePath; this.#content = content; this.#context = context; } // eslint-disable-next-line @typescript-eslint/require-await async lint() { try { const source = parseManifest(this.#content); this.#reporter = new ManifestReporter(this.#resourcePath, this.#context, source); this.#analyzeManifest(source); } catch (err) { const message = err instanceof Error ? err.message : String(err); this.#context.addLintingMessage(this.#resourcePath, { id: MESSAGE.PARSING_ERROR, args: { message } }); } } #analyzeManifest(source) { const manifest = source.data; const minUI5Version = manifest?.["sap.ui5"]?.dependencies?.minUI5Version; if (minUI5Version) { // Simplify version extraction - handle both string and array cases const availableVersions = Array.isArray(minUI5Version) ? minUI5Version : (typeof minUI5Version === "string" ? [minUI5Version] : []); // Check if any version is below 1.136 const isBelow136 = availableVersions.some((version) => { const normalizedVersion = semver.coerce(version); return normalizedVersion && semver.lt(normalizedVersion, "1.136.0"); }); if (isBelow136) { this.#reporter?.addMessage(MESSAGE.NO_LEGACY_UI5_VERSION_IN_MANIFEST, "/sap.ui5/dependencies/minUI5Version"); } } if (manifest?._version?.startsWith("2.")) { this.#validatePropertiesForManifestVersion(source, true); } else { this.#validatePropertiesForManifestVersion(source); this.#reporter?.addMessage(MESSAGE.NO_OUTDATED_MANIFEST_VERSION, "/_version"); } } #validatePropertiesForManifestVersion(source, isManifest2 = false) { const manifest = source.data; const { resources, models, dependencies, rootView, routing } = (manifest["sap.ui5"] ?? {}); const { dataSources } = (manifest["sap.app"] ?? {}); // Validate async flags for manifest version 2 if (isManifest2) { this.#validateAsyncFlagsForManifestV2(rootView, routing); } // Detect deprecated libraries: const libKeys = (dependencies?.libs && Object.keys(dependencies.libs)) ?? []; libKeys.forEach((libKey) => { if (deprecatedLibraries.includes(libKey)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_LIBRARY, { libraryName: libKey, }, `/sap.ui5/dependencies/libs/${libKey}`); } }); // Detect deprecated components: const componentKeys = (dependencies?.components && Object.keys(dependencies.components)) ?? []; componentKeys.forEach((componentKey) => { if (deprecatedComponents.includes(componentKey)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_COMPONENT, { componentName: componentKey, }, `/sap.ui5/dependencies/components/${componentKey}`); } }); // Detect deprecated type of rootView: if (typeof rootView === "object" && rootView.type && deprecatedViewTypes.includes(rootView.type)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_VIEW_TYPE, { viewType: rootView.type, }, "/sap.ui5/rootView/type"); } // Detect deprecated view type in routing.config: if (routing?.config?.viewType && deprecatedViewTypes.includes(routing.config.viewType)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_VIEW_TYPE, { viewType: routing.config.viewType, }, "/sap.ui5/routing/config/viewType"); } // Detect deprecations in routing.targets: const targets = routing?.targets; if (targets) { for (const [key, target] of Object.entries(targets)) { // Check if name starts with module and viewType is defined: const name = target.name ?? target.viewName; if (name && name.startsWith("module:")) { if (target.viewType) { this.#reporter?.addMessage(MESSAGE.REDUNDANT_VIEW_CONFIG_PROPERTY, { propertyName: "viewType", }, `/sap.ui5/routing/targets/${key}/viewType`); } } const pathToViewObject = `/sap.ui5/routing/targets/${key}`; // Detect deprecated view type: if (target.viewType && deprecatedViewTypes.includes(target.viewType)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_VIEW_TYPE, { viewType: target.viewType, }, `${pathToViewObject}/viewType`); } } } if (resources?.js) { const key = "/sap.ui5/resources/js"; let fix; const messageId = isManifest2 ? MESSAGE.NO_REMOVED_MANIFEST_PROPERTY : MESSAGE.DEPRECATED_MANIFEST_JS_RESOURCES; if (!resources.js.length) { // If there are no js resources, we can remove the whole array and // if the array is the only property of "resources", we can also remove // the "resources" object fix = new RemoveJsonPropertyFix({ key, pointers: source.pointers, removeEmptyDirectParent: true, }); } this.#reporter?.addMessage(messageId, { propName: key }, key, fix); } const modelKeys = (models && Object.keys(models)) ?? []; modelKeys.forEach((modelKey) => { const curModel = (models?.[modelKey]) ?? {}; if (!curModel.type) { const curDataSource = dataSources && curModel.dataSource && dataSources[curModel.dataSource]; if (curDataSource && /* if not provided dataSource.type="OData" */ (curDataSource.type === "OData" || !curDataSource.type)) { curModel.type = curDataSource.settings?.odataVersion === "4.0" ? "sap.ui.model.odata.v4.ODataModel" : "sap.ui.model.odata.v2.ODataModel"; } // There are other types that can be found in sap/ui/core/Component, but the one // we actually care here is just the "sap.ui.model.odata.v4.ODataModel" } if (curModel.type && [ "sap.ui.model.odata.ODataModel", "sap.zen.dsh.widgets.SDKModel", ].includes(curModel.type)) { this.#reporter?.addMessage(MESSAGE.DEPRECATED_CLASS, { className: curModel.type, details: `{@link ${curModel.type}}`, }, `/sap.ui5/models/${modelKey}/type`); } if (curModel.type === "sap.ui.model.odata.v4.ODataModel" && curModel.settings && "synchronizationMode" in curModel.settings) { const key = `/sap.ui5/models/${modelKey}/settings/synchronizationMode`; const fix = new RemoveJsonPropertyFix({ key, pointers: source.pointers, removeEmptyDirectParent: true, }); this.#reporter?.addMessage(MESSAGE.DEPRECATED_ODATA_MODEL_V4_SYNCHRONIZATION_MODE, { modelName: modelKey, }, key, fix); } }); } #validateAsyncFlagsForManifestV2(rootView, routing) { // Check rootView async flag if (typeof rootView === "object" && rootView && "async" in rootView && typeof rootView.async === "boolean") { // Error: async is redundant in manifest v2 this.#reporter?.addMessage(MESSAGE.NO_REMOVED_MANIFEST_PROPERTY, { propName: "/sap.ui5/rootView/async", }, "/sap.ui5/rootView/async"); } // Check routing config async flag if (routing?.config && "async" in routing.config && typeof routing.config.async === "boolean") { // Error: async is redundant in manifest v2 this.#reporter?.addMessage(MESSAGE.NO_REMOVED_MANIFEST_PROPERTY, { propName: "/sap.ui5/routing/config/async", }, "/sap.ui5/routing/config/async"); } } } //# sourceMappingURL=ManifestLinter.js.map