UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

123 lines (122 loc) 4.1 kB
import { escapeRegExp, regEx } from "../../../util/regex.js"; //#region lib/modules/manager/ant/properties.ts const fullPlaceholderRegex = regEx(/^\$\{([^}]+)}$/); const placeholderTestRegex = regEx(/\$\{[^}]+}/); const propertySeparatorRegex = regEx(/^([^=:\s]+)\s*[=:\s]\s*(.*)$/); function containsPlaceholder(str) { return !!str && placeholderTestRegex.test(str); } /** * Find the byte offset of an attribute's value in raw XML content. * Returns the offset of the first character of the value (after the opening quote). */ function findAttrValuePosition(content, node, attrName) { const startTag = node.startTagPosition; const tagEnd = content.indexOf(">", startTag); const tagContent = content.slice(startTag, tagEnd + 1); const match = regEx(`${escapeRegExp(attrName)}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`).exec(tagContent); const valueInMatch = match[1] ?? match[2]; const valueOffset = match[0].indexOf(valueInMatch); return startTag + match.index + valueOffset; } /** * Parse a .properties file into a map of property names to AntProp. * Implements first-definition-wins: if a key already exists in the map, it is not overwritten. */ function parsePropertiesFile(content, packageFile, props) { let offset = 0; for (const rawLine of content.split("\n")) { const line = rawLine.trim(); if (line.startsWith("#") || line.startsWith("!") || line === "") { offset += rawLine.length + 1; continue; } const separatorMatch = propertySeparatorRegex.exec(line); if (separatorMatch) { const key = separatorMatch[1]; const val = separatorMatch[2].trim(); if (!(key in props)) props[key] = { val, fileReplacePosition: offset + rawLine.indexOf(line) + line.indexOf(separatorMatch[2]), packageFile }; } offset += rawLine.length + 1; } } /** * Apply property resolution to a dependency. * Handles chained references with circular detection. */ function applyProps(dep, depPackageFile, props) { const currentValue = dep.currentValue; if (!currentValue || !containsPlaceholder(currentValue)) return dep; const fullMatch = fullPlaceholderRegex.exec(currentValue); if (!fullMatch) { dep.skipReason = "version-placeholder"; return dep; } const propKey = fullMatch[1]; const prop = props[propKey]; if (!prop) { dep.skipReason = "version-placeholder"; return dep; } if (containsPlaceholder(prop.val)) { dep.skipReason = "recursive-placeholder"; return dep; } dep.currentValue = prop.val; dep.sharedVariableName = propKey; dep.fileReplacePosition = prop.fileReplacePosition; if (prop.packageFile !== depPackageFile) dep.editFile = prop.packageFile; dep.propSource = prop.packageFile; return dep; } /** * Resolve a single property key, following chained references. * Returns the resolved value or null if circular/unresolvable. */ function resolveKey(key, props, resolved, chain) { if (resolved.has(key)) return resolved.get(key); if (chain.has(key)) { resolved.set(key, null); return null; } const prop = props[key]; if (!prop) return null; if (!containsPlaceholder(prop.val)) { resolved.set(key, prop.val); return prop.val; } chain.add(key); let isCircular = false; const val = prop.val.replace(regEx(/\$\{([^}]+)}/g), (match, refKey) => { const refResult = resolveKey(refKey, props, resolved, chain); if (refResult === null) { isCircular = true; return match; } return refResult; }); chain.delete(key); if (isCircular) { resolved.set(key, null); return null; } resolved.set(key, val); prop.val = val; return val; } /** * Resolve chained property references within the property map itself. * E.g., if prop A = "${B}" and prop B = "1.0", resolve A to "1.0". * Marks circular properties by setting val to a placeholder that will be caught later. */ function resolveChainedProps(props) { const resolved = /* @__PURE__ */ new Map(); for (const key of Object.keys(props)) resolveKey(key, props, resolved, /* @__PURE__ */ new Set()); } //#endregion export { applyProps, containsPlaceholder, findAttrValuePosition, parsePropertiesFile, resolveChainedProps }; //# sourceMappingURL=properties.js.map