Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"defaultAggregation": "{{metadata.defaultAggregation}}",

"properties": {
{{#each metadata.properties}}
{{#each metadata.properties}}{{#if (lookup (lookup @root.jsDoc "properties") @key)}}
{{{lookup (lookup @root.jsDoc "properties") @key}}}
"{{@key}}": {{{json this}}},
{{/each}}
{{/if}}{{/each}}
},

"aggregations": {
Expand Down
219 changes: 110 additions & 109 deletions packages/ui5-tooling-modules/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ function isValidVersion(version) {
}
}

/**
* converts a wildcard pattern to a regular expression
* @param {string} pattern the pattern to convert
* @returns {RegExp} the regular expression
*/
function wildcardToRegex(pattern) {
// escape special characters
const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
// replace the wildcard with a capture group
const regexPattern = escapedPattern.replace(/\*/g, "(.*)");
// return the regular expression
return new RegExp(`^${regexPattern}$`);
}

/**
* helper to check the existence of a resource (case-sensitive)
* @param {string} file file path
Expand Down Expand Up @@ -216,49 +230,34 @@ function findDependency(dep, cwd = process.cwd(), depPaths = []) {
}

/**
* find the dependencies of the current project and its transitive dependencies
* (excluding devDependencies and providedDependencies)
* @param {object} options options
* @param {string} [options.cwd] current working directory
* @param {string[]} [options.depPaths] list of dependency paths
* @param {boolean} [options.linkedOnly] find only the linked dependencies
* @param {string[]} [options.additionalDeps] list of additional dependencies (e.g. dev dependencies to include)
* @param {string[]} [knownDeps] list of known dependencies
* @returns {string[]} array of dependency root directories
* finds the package.json file for the given module path
* @param {string} modulePath the path of the module
* @returns {string} the path of the package.json file
*/
function findDependencies({ cwd = process.cwd(), depPaths = [], linkedOnly, additionalDeps = [] } = {}, knownDeps = []) {
const pkgJson = getPackageJson(path.join(cwd, "package.json"));
let dependencies = [...Object.keys(pkgJson.dependencies || {}), ...Object.keys(pkgJson.optionalDependencies || {})];
if (additionalDeps?.length > 0) {
dependencies = dependencies.concat(additionalDeps);
}
if (linkedOnly) {
dependencies = dependencies.filter((dep) => {
const version = pkgJson.dependencies?.[dep] || pkgJson.optionalDependencies?.[dep];
return !isValidVersion(version);
});
}
const depRoots = [];
const findDeps = [];
for (const dep of dependencies) {
const npmPackage = npmPackageScopeRegEx.exec(dep)?.[1];
if (knownDeps.indexOf(npmPackage) !== -1) {
continue;
function findPackageJson(modulePath) {
let pkgJsonFile;
// lookup the parent dirs recursive to find package.json
const nodeModules = `${path.sep}node_modules${path.sep}`;
if (modulePath.lastIndexOf(nodeModules) > -1) {
const localModuleRootPath = modulePath.substring(0, modulePath.lastIndexOf(nodeModules) + nodeModules.length);
const localModuleName = modulePath.substring(localModuleRootPath.length)?.replace(/\\/g, "/");
const [, npmPackage] = npmPackageScopeRegEx.exec(localModuleName) || [];
pkgJsonFile = path.join(localModuleRootPath, npmPackage, "package.json");
if (!existsSyncAndIsFile(pkgJsonFile)) {
pkgJsonFile = undefined;
}
knownDeps.push(npmPackage);
const depPath = findDependency(path.posix.join(npmPackage, "package.json"), cwd, depPaths);
let depRoot = depPath && path.dirname(depPath);
if (depRoot && depRoots.indexOf(depRoot) === -1) {
depRoots.push(depRoot);
// delay the dependency lookup to avoid finding transitive dependencies before local dependencies
findDeps.push({ cwd: depRoot, depPaths, linkedOnly, additionalDeps });
} else {
let dir = path.dirname(modulePath);
while (dir !== path.dirname(dir)) {
const pkgJson = path.join(dir, "package.json");
if (existsSyncAndIsFile(pkgJson)) {
pkgJsonFile = pkgJson;
break;
}
dir = path.dirname(dir);
}
}
// lookup the transitive dependencies
for (const dep of findDeps) {
depRoots.push(...findDependencies(dep, knownDeps));
}
return depRoots;
return pkgJsonFile;
}

/**
Expand Down Expand Up @@ -294,6 +293,7 @@ class BundleInfoCache {
}
}
}

/**
* bundle info object containing all entries
*/
Expand Down Expand Up @@ -344,89 +344,90 @@ class BundleInfo {
}
}

// local cache of resolved module paths
const modulesCache = {};
// the utiltiy module
module.exports = function (log, projectInfo) {
// performande metrics
const perfmetrics = {
resolveModulesTime: 0,
resolveModules: {},
};

// local cache of negative modules (avoid additional lookups)
const modulesNegativeCache = [];
// local cache of resolved module paths
const modulesCache = {};

// local cache for package.json files
const packageJsonCache = {};
// local cache of negative modules (avoid additional lookups)
const modulesNegativeCache = [];

// local cache for dependencies list
const findDependenciesCache = {};
// local cache for package.json files
const packageJsonCache = {};

// performance metrics
const perfmetrics = {
resolveModulesTime: 0,
resolveModules: {},
};
// local cache for dependencies list
const findDependenciesCache = {};

/**
* load and cache the package.json file for the given path
* @param {string} pkgJsonFile the path of the package.json file
* @returns {object} the package.json content
*/
function getPackageJson(pkgJsonFile) {
if (!packageJsonCache[pkgJsonFile]) {
try {
const pkgJson = JSON.parse(readFileSync(pkgJsonFile, { encoding: "utf8" }));
packageJsonCache[pkgJsonFile] = pkgJson;
} catch (err) {
console.error(`Failed to read package.json file ${pkgJsonFile}!`, err);
throw err;
/**
* load and cache the package.json file for the given path
* @param {string} pkgJsonFile the path of the package.json file
* @returns {object} the package.json content
*/
function getPackageJson(pkgJsonFile) {
if (!packageJsonCache[pkgJsonFile]) {
try {
const pkgJson = JSON.parse(readFileSync(pkgJsonFile, { encoding: "utf8" }));
packageJsonCache[pkgJsonFile] = pkgJson;
} catch (err) {
console.error(`Failed to read package.json file ${pkgJsonFile}!`, err);
throw err;
}
}
return packageJsonCache[pkgJsonFile];
}
return packageJsonCache[pkgJsonFile];
}

/**
* finds the package.json file for the given module path
* @param {string} modulePath the path of the module
* @returns {string} the path of the package.json file
*/
function findPackageJson(modulePath) {
let pkgJsonFile;
// lookup the parent dirs recursive to find package.json
const nodeModules = `${path.sep}node_modules${path.sep}`;
if (modulePath.lastIndexOf(nodeModules) > -1) {
const localModuleRootPath = modulePath.substring(0, modulePath.lastIndexOf(nodeModules) + nodeModules.length);
const localModuleName = modulePath.substring(localModuleRootPath.length)?.replace(/\\/g, "/");
const [, npmPackage] = npmPackageScopeRegEx.exec(localModuleName) || [];
pkgJsonFile = path.join(localModuleRootPath, npmPackage, "package.json");
if (!existsSyncAndIsFile(pkgJsonFile)) {
pkgJsonFile = undefined;
/**
* find the dependencies of the current project and its transitive dependencies
* (excluding devDependencies and providedDependencies)
* @param {object} options options
* @param {string} [options.cwd] current working directory
* @param {string[]} [options.depPaths] list of dependency paths
* @param {boolean} [options.linkedOnly] find only the linked dependencies
* @param {string[]} [options.additionalDeps] list of additional dependencies (e.g. dev dependencies to include)
* @param {string[]} [knownDeps] list of known dependencies
* @returns {string[]} array of dependency root directories
*/
function findDependencies({ cwd = process.cwd(), depPaths = [], linkedOnly, additionalDeps = [] } = {}, knownDeps = []) {
const pkgJson = getPackageJson(path.join(cwd, "package.json"));
let dependencies = [...Object.keys(pkgJson.dependencies || {}), ...Object.keys(pkgJson.optionalDependencies || {})];
if (additionalDeps?.length > 0) {
dependencies = dependencies.concat(additionalDeps);
}
} else {
let dir = path.dirname(modulePath);
while (dir !== path.dirname(dir)) {
const pkgJson = path.join(dir, "package.json");
if (existsSyncAndIsFile(pkgJson)) {
pkgJsonFile = pkgJson;
break;
if (linkedOnly) {
dependencies = dependencies.filter((dep) => {
const version = pkgJson.dependencies?.[dep] || pkgJson.optionalDependencies?.[dep];
return !isValidVersion(version);
});
}
const depRoots = [];
const findDeps = [];
for (const dep of dependencies) {
const npmPackage = npmPackageScopeRegEx.exec(dep)?.[1];
if (knownDeps.indexOf(npmPackage) !== -1) {
continue;
}
knownDeps.push(npmPackage);
const depPath = findDependency(path.posix.join(npmPackage, "package.json"), cwd, depPaths);
let depRoot = depPath && path.dirname(depPath);
if (depRoot && depRoots.indexOf(depRoot) === -1) {
depRoots.push(depRoot);
// delay the dependency lookup to avoid finding transitive dependencies before local dependencies
findDeps.push({ cwd: depRoot, depPaths, linkedOnly, additionalDeps });
}
dir = path.dirname(dir);
}
// lookup the transitive dependencies
for (const dep of findDeps) {
depRoots.push(...findDependencies(dep, knownDeps));
}
return depRoots;
}
return pkgJsonFile;
}

/**
* converts a wildcard pattern to a regular expression
* @param {string} pattern the pattern to convert
* @returns {RegExp} the regular expression
*/
function wildcardToRegex(pattern) {
// escape special characters
const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
// replace the wildcard with a capture group
const regexPattern = escapedPattern.replace(/\*/g, "(.*)");
// return the regular expression
return new RegExp(`^${regexPattern}$`);
}

// the utiltiy module
module.exports = function (log, projectInfo) {
/**
* Checks whether the file behind the given path is a module to skip for transformation or not
* @param {string} source the path of a JS module
Expand Down
2 changes: 1 addition & 1 deletion packages/ui5-tooling-modules/lib/utils/DTSSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ const DTSSerializer = {
if (classDef.superclass) {
// Set up superclass information
let superclassSettings;
if (WebComponentRegistryHelper.isUI5Element(classDef.superclass)) {
if (WebComponentRegistryHelper.isUI5Element(classDef.superclass) || WebComponentRegistryHelper.isWebComponent(classDef.superclass)) {
superclassSettings = {
class: "WebComponent",
module: "sap/ui/core/webc/WebComponent",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui5-tooling-modules/lib/utils/JSDocSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function dot2slash(s) {
function _serializeClassHeader(classDef, jsdocTags) {
// find superclass name, either another wrapper OR the core WebComponent base class
let superclassName = classDef.superclass.name;
if (superclassName === WebComponentRegistryHelper.UI5_ELEMENT_CLASS_NAME) {
if (WebComponentRegistryHelper.isUI5Element(classDef.superclass) || WebComponentRegistryHelper.isWebComponent(classDef.superclass)) {
// we reached the very top of the inheritance chain
superclassName = "sap.ui.core.webc.WebComponent";
} else if (superclassName) {
Expand Down
12 changes: 10 additions & 2 deletions packages/ui5-tooling-modules/lib/utils/WebComponentRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,14 @@ class RegistryEntry {
classDef.superclass = superclassRef;
}
} else if (classDef.customElement && !WebComponentRegistryHelper.isUI5Element(classDef)) {
/*
logger.warn(
`⚠️ The class '${this.namespace}/${classDef.name}' is a custom element not extending '${WebComponentRegistryHelper.UI5_ELEMENT_NAMESPACE}/${WebComponentRegistryHelper.UI5_ELEMENT_CLASS_NAME}!`,
);
*/
// non UI5Elements are extending the base class for Web Components
classDef.superclass = {
name: WebComponentRegistryHelper.UI5_ELEMENT_CLASS_NAME,
namespace: WebComponentRegistryHelper.UI5_ELEMENT_NAMESPACE,
name: "sap.ui.core.webc.WebComponent",
};
}
}
Expand Down Expand Up @@ -950,6 +955,9 @@ class RegistryEntry {

// TODO: This whole method needs to be adapted to correctly write JSDoc

// TODO: externalize special cases like Label, MultiInput, ShellBar etc. into e.g. .ui5webcrc config file
// within the project wrapping the web components (e.g. the application or the library project)

// The label has a couple of specifics that are not fully reflected in the custom elements.
if (tag.includes("ui5-label")) {
// the ui5-label has as default slot, but no aggregations on the retrofit layer...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const WebComponentRegistryHelper = {
isUI5Element(ui5Superclass) {
return ui5Superclass.namespace === this.UI5_ELEMENT_NAMESPACE && ui5Superclass.name === this.UI5_ELEMENT_CLASS_NAME;
},

isWebComponent(ui5Superclass) {
return ui5Superclass.name === "sap.ui.core.webc.WebComponent";
},
};

module.exports = WebComponentRegistryHelper;
5 changes: 0 additions & 5 deletions packages/ui5-tooling-modules/test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,11 +551,6 @@ test.serial("Verify generation of @luigi-project/container", async (t) => {
}),
{
keepDynamicImports: ["@luigi-project/container"],
pluginOptions: {
webcomponents: {
scoping: false,
},
},
},
);
const modPackage = await env.getModule("@luigi-project/container");
Expand Down
7 changes: 4 additions & 3 deletions showcases/ui5-tsapp/ui5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ customConfiguration:
config-ui5-tooling-modules: &cfgModules
debug: true
minify: true
pluginOptions:
webcomponents:
scoping: false
watch:
- "../ui5-tsapp-webc/webapp"
pluginOptions:
webcomponents:
skipJSDoc: false # enable the JSDoc generation
skipDtsGeneration: false
type: application
framework:
name: OpenUI5
Expand Down
Loading