dotnet-deps-parser
Version:
Generate a dep tree given a collection of manifests
376 lines • 17.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProjectChildren = exports.getTargetFrameworksFromProjectAssetsJson = exports.getTargetFrameworksFromProjectJson = exports.getTargetFrameworksFromProjectConfig = exports.getSdkFromProjectFile = exports.getTargetFrameworksFromProjectFile = exports.getPropertiesMap = exports.parseXmlFile = exports.getDependenciesFromPackageReference = exports.getDependencyTreeFromProjectFile = exports.getDependencyTreeFromPackagesConfig = exports.getDependencyTreeFromProjectJson = exports.ProjectJsonDepType = exports.DepType = void 0;
const parseXML = require("xml2js");
const lodash_1 = require("lodash");
const error_catalog_nodejs_public_1 = require("@snyk/error-catalog-nodejs-public");
const case_insensitive_1 = require("./case-insensitive");
var DepType;
(function (DepType) {
DepType["prod"] = "prod";
DepType["dev"] = "dev";
})(DepType || (exports.DepType = DepType = {}));
var ProjectJsonDepType;
(function (ProjectJsonDepType) {
ProjectJsonDepType["build"] = "build";
ProjectJsonDepType["project"] = "project";
ProjectJsonDepType["platform"] = "platform";
ProjectJsonDepType["default"] = "default";
})(ProjectJsonDepType || (exports.ProjectJsonDepType = ProjectJsonDepType = {}));
function getDependencyTreeFromProjectJson(manifestFile, includeDev = false) {
const depTree = {
dependencies: {},
hasDevDependencies: false,
name: '',
version: '',
};
for (const depName in manifestFile.dependencies) {
if (!manifestFile.dependencies.hasOwnProperty(depName)) {
continue;
}
const depValue = manifestFile.dependencies[depName];
const version = depValue.version || depValue;
const isDev = depValue.type === 'build';
depTree.hasDevDependencies = depTree.hasDevDependencies || isDev;
if (isDev && !includeDev) {
continue;
}
depTree.dependencies[depName] = buildSubTreeFromProjectJson(depName, version, isDev);
}
return depTree;
}
exports.getDependencyTreeFromProjectJson = getDependencyTreeFromProjectJson;
function buildSubTreeFromProjectJson(name, version, isDev) {
const depSubTree = {
depType: isDev ? DepType.dev : DepType.prod,
dependencies: {},
name,
version,
};
return depSubTree;
}
async function getDependencyTreeFromPackagesConfig(manifestFile, includeDev = false) {
const depTree = {
dependencies: {},
hasDevDependencies: false,
name: '',
version: '',
};
const packageList = (0, case_insensitive_1.getAll)((0, case_insensitive_1.getCaseInsensitive)(manifestFile, 'packages'), 'package');
for (const dep of packageList) {
const depName = (0, case_insensitive_1.getAttr)(dep, 'id');
const isDev = !!(0, case_insensitive_1.getAttr)(dep, 'developmentDependency');
depTree.hasDevDependencies = depTree.hasDevDependencies || isDev;
if (isDev && !includeDev) {
continue;
}
depTree.dependencies[depName] = buildSubTreeFromPackagesConfig(dep, isDev);
}
return depTree;
}
exports.getDependencyTreeFromPackagesConfig = getDependencyTreeFromPackagesConfig;
function buildSubTreeFromPackagesConfig(dep, isDev) {
const depSubTree = {
depType: isDev ? DepType.dev : DepType.prod,
dependencies: {},
name: (0, case_insensitive_1.getAttr)(dep, 'id'),
version: (0, case_insensitive_1.getAttr)(dep, 'version'),
};
const targetFramework = (0, case_insensitive_1.getAttr)(dep, 'targetFramework');
if (targetFramework) {
depSubTree.targetFrameworks = [targetFramework];
}
return depSubTree;
}
async function getDependencyTreeFromProjectFile(manifestFile, includeDev = false, propsMap = {}) {
var _a, _b;
const nameProperty = getProjectChildren(manifestFile, 'PropertyGroup')
.filter((propertyGroup) => typeof propertyGroup !== 'string')
.find((propertyGroup) => {
return ((0, case_insensitive_1.hasKey)(propertyGroup, 'PackageId') ||
(0, case_insensitive_1.hasKey)(propertyGroup, 'AssemblyName'));
}) || {};
const name = ((_a = (0, case_insensitive_1.getCaseInsensitive)(nameProperty, 'PackageId')) === null || _a === void 0 ? void 0 : _a[0]) ||
((_b = (0, case_insensitive_1.getCaseInsensitive)(nameProperty, 'AssemblyName')) === null || _b === void 0 ? void 0 : _b[0]) ||
'';
const packageReferenceDeps = await getDependenciesFromPackageReference(manifestFile, includeDev, propsMap);
// order matters, the order deps are parsed in needs to be preserved and first seen kept
// so applying the packageReferenceDeps last to override the second parsed
const depTree = {
dependencies: Object.assign({}, packageReferenceDeps.dependencies),
hasDevDependencies: packageReferenceDeps.hasDevDependencies,
name,
version: '',
};
if (packageReferenceDeps.dependenciesWithUnknownVersions) {
depTree.dependenciesWithUnknownVersions =
packageReferenceDeps.dependenciesWithUnknownVersions;
}
return depTree;
}
exports.getDependencyTreeFromProjectFile = getDependencyTreeFromProjectFile;
async function getDependenciesFromPackageReference(manifestFile, includeDev = false, propsMap) {
let dependenciesResult = {
dependencies: {},
hasDevDependencies: false,
};
const packageGroups = getProjectChildren(manifestFile, 'ItemGroup').filter((itemGroup) => typeof itemGroup === 'object' && (0, case_insensitive_1.hasKey)(itemGroup, 'PackageReference'));
if (!packageGroups.length) {
return dependenciesResult;
}
for (const packageList of packageGroups) {
dependenciesResult = processItemGroupForPackageReference(packageList, manifestFile, includeDev, dependenciesResult, propsMap);
}
return dependenciesResult;
}
exports.getDependenciesFromPackageReference = getDependenciesFromPackageReference;
function processItemGroupForPackageReference(packageList, manifestFile, includeDev, dependenciesResult, propsMap) {
var _a;
const targetFrameworks = ((_a = (0, case_insensitive_1.getAttr)(packageList, 'Condition')) !== null && _a !== void 0 ? _a : false)
? getConditionalFrameworks((0, case_insensitive_1.getAttr)(packageList, 'Condition'))
: [];
for (const dep of (0, case_insensitive_1.getAll)(packageList, 'PackageReference')) {
const depName = (0, case_insensitive_1.getAttr)(dep, 'Include');
if (!depName) {
// PackageReference Update is not yet supported
continue;
}
const isDev = !!(0, case_insensitive_1.getAttr)(dep, 'developmentDependency');
dependenciesResult.hasDevDependencies =
dependenciesResult.hasDevDependencies || isDev;
if (isDev && !includeDev) {
continue;
}
const subDep = buildSubTreeFromPackageReference(dep, isDev, manifestFile, targetFrameworks, propsMap);
if (subDep.withoutVersion) {
dependenciesResult.dependenciesWithUnknownVersions =
dependenciesResult.dependenciesWithUnknownVersions || [];
dependenciesResult.dependenciesWithUnknownVersions.push(subDep.name);
}
else {
dependenciesResult.dependencies[depName] = subDep;
}
}
return dependenciesResult;
}
function buildSubTreeFromPackageReference(dep, isDev, manifestFile, targetFrameworks, propsMap) {
const version = extractDependencyVersion(dep, manifestFile, propsMap) || '';
if (!(0, lodash_1.isEmpty)(version)) {
const depSubTree = {
depType: isDev ? DepType.dev : DepType.prod,
dependencies: {},
name: (0, case_insensitive_1.getAttr)(dep, 'Include'),
// Version could be in attributes or as child node.
version,
};
if (targetFrameworks.length) {
depSubTree.targetFrameworks = targetFrameworks;
}
return depSubTree;
}
else {
return { name: (0, case_insensitive_1.getAttr)(dep, 'Include'), withoutVersion: true };
}
}
function extractDependencyVersion(dep, manifestFile, propsMap) {
var _a;
const VARS_MATCHER = /^\$\((.*?)\)/;
let version = (0, case_insensitive_1.getAttr)(dep, 'Version') || (0, case_insensitive_1.getCaseInsensitive)(dep, 'Version');
if (Array.isArray(version)) {
version = version[0];
}
const variableVersion = version && version.match(VARS_MATCHER);
if (!variableVersion) {
return version;
}
// version is a variable, extract it from manifest or props lookup
const propertyName = variableVersion[1];
const propertyMap = Object.assign(Object.assign({}, propsMap), getPropertiesMap(manifestFile));
return (_a = (0, case_insensitive_1.getCaseInsensitive)(propertyMap, propertyName)) !== null && _a !== void 0 ? _a : null;
}
function getConditionalFrameworks(condition) {
const regexp = /\(TargetFramework\)'\s?==\s? '((\w|\d|\.)*)'/gi;
const frameworks = [];
let match = regexp.exec(condition);
while (match !== null) {
frameworks.push(match[1]);
match = regexp.exec(condition);
}
return frameworks;
}
async function parseXmlFile(manifestFileContents) {
return new Promise((resolve, reject) => {
parseXML.parseString(manifestFileContents, (err, result) => {
if (err) {
const e = new error_catalog_nodejs_public_1.OpenSourceEcosystems.UnparseableManifestError('Manifest xml file parsing failed');
return reject(e);
}
return resolve(result);
});
});
}
exports.parseXmlFile = parseXmlFile;
function getPropertiesMap(propsContents) {
const projectPropertyGroup = getProjectChildren(propsContents, 'PropertyGroup');
const props = {};
if (!projectPropertyGroup.length) {
return props;
}
for (const group of projectPropertyGroup) {
// Skip empty property groups that are parsed as strings
if (typeof group === 'string') {
continue;
}
for (const key of Object.keys(group)) {
(0, lodash_1.set)(props, key, group[key][0]);
}
}
return props;
}
exports.getPropertiesMap = getPropertiesMap;
function getTargetFrameworksFromProjectFile(manifestFile) {
let targetFrameworksResult = [];
// First, look in direct PropertyGroup elements
const projectPropertyGroup = getProjectChildren(manifestFile, 'PropertyGroup');
let propertyList = {};
if (projectPropertyGroup) {
try {
propertyList =
projectPropertyGroup
.filter((propertyGroup) => typeof propertyGroup === 'object')
.find((propertyGroup) => {
return hasTargetFrameworkProperty(propertyGroup);
}) || {};
}
catch (err) {
propertyList = {};
}
}
// If no target framework found in direct PropertyGroups, look inside Choose/When blocks
if ((0, lodash_1.isEmpty)(propertyList)) {
const chooseElements = getProjectChildren(manifestFile, 'Choose');
for (const choose of chooseElements) {
const whenElements = (0, case_insensitive_1.getAll)(choose, 'When');
for (const when of whenElements) {
const whenPropertyGroups = (0, case_insensitive_1.getAll)(when, 'PropertyGroup');
try {
const foundProperty = whenPropertyGroups
.filter((propertyGroup) => typeof propertyGroup === 'object')
.find((propertyGroup) => {
return hasTargetFrameworkProperty(propertyGroup);
});
if (foundProperty && !(0, lodash_1.isEmpty)(foundProperty)) {
propertyList = foundProperty;
break;
}
}
catch (err) {
// Continue searching other When blocks
}
}
if (!(0, lodash_1.isEmpty)(propertyList)) {
break;
}
}
}
if ((0, lodash_1.isEmpty)(propertyList)) {
return targetFrameworksResult;
}
const targetFrameworksProp = (0, case_insensitive_1.getCaseInsensitive)(propertyList, 'TargetFrameworks');
const targetFrameworkVersionProp = (0, case_insensitive_1.getCaseInsensitive)(propertyList, 'TargetFrameworkVersion');
let targetFrameworkProp = (0, case_insensitive_1.getCaseInsensitive)(propertyList, 'TargetFramework');
// TargetFrameworks is expected to be a list ; separated
if (targetFrameworksProp) {
for (const item of targetFrameworksProp) {
targetFrameworksResult = [
...targetFrameworksResult,
...getTargetFrameworks(item),
];
}
}
// TargetFrameworkVersion is expected to be a string containing only one item
// TargetFrameworkVersion also implies .NETFramework, for convenience
// return longer version
if (targetFrameworkVersionProp) {
targetFrameworksResult.push(`.NETFramework,Version=${targetFrameworkVersionProp[0]}`);
}
// TargetFrameworks is expected to be a string
if (targetFrameworkProp) {
// sanity check
if (Array.isArray(targetFrameworkProp)) {
// mutate the array to effectively "ignore" conditions
const frameworks = targetFrameworkProp.map((framework) => {
if (framework &&
typeof framework === 'object' &&
Object.hasOwnProperty.call(framework, '_')) {
return framework._;
}
return framework;
});
targetFrameworkProp = frameworks
.map((x) => x.trim())
.filter((x) => !(0, lodash_1.isEmpty)(x));
}
targetFrameworksResult = [
...targetFrameworksResult,
...targetFrameworkProp,
];
}
return (0, lodash_1.uniq)(targetFrameworksResult);
}
exports.getTargetFrameworksFromProjectFile = getTargetFrameworksFromProjectFile;
// Extracts the SDK name for SDK-style projects, based on documentation at
// https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview.
function getSdkFromProjectFile(manifestFile) {
const project = (0, case_insensitive_1.getCaseInsensitive)(manifestFile, 'Project');
const projectSdkAttribute = (0, case_insensitive_1.getAttr)(project, 'Sdk');
const topLevelSdkElement = (0, case_insensitive_1.getAttr)((0, case_insensitive_1.getAll)(project, 'Sdk')[0], 'Name');
return projectSdkAttribute || topLevelSdkElement;
}
exports.getSdkFromProjectFile = getSdkFromProjectFile;
function getTargetFrameworks(item) {
if (typeof item === 'object' && Object.hasOwnProperty.call(item, '_')) {
item = item._;
}
return item
.split(';')
.map((x) => x.trim())
.filter((x) => !(0, lodash_1.isEmpty)(x));
}
function getTargetFrameworksFromProjectConfig(manifestFile) {
const targetFrameworksResult = [];
const packages = (0, case_insensitive_1.getAll)((0, case_insensitive_1.getCaseInsensitive)(manifestFile, 'packages'), 'package');
for (const item of packages) {
const targetFramework = (0, case_insensitive_1.getAttr)(item, 'targetFramework');
if (!targetFramework) {
continue;
}
if (!targetFrameworksResult.includes(targetFramework)) {
targetFrameworksResult.push(targetFramework);
}
}
return targetFrameworksResult;
}
exports.getTargetFrameworksFromProjectConfig = getTargetFrameworksFromProjectConfig;
function getTargetFrameworksFromProjectJson(manifestFile) {
var _a;
return Object.keys((_a = manifestFile === null || manifestFile === void 0 ? void 0 : manifestFile.frameworks) !== null && _a !== void 0 ? _a : {});
}
exports.getTargetFrameworksFromProjectJson = getTargetFrameworksFromProjectJson;
function getTargetFrameworksFromProjectAssetsJson(manifestFile) {
var _a;
return Object.keys((_a = manifestFile === null || manifestFile === void 0 ? void 0 : manifestFile.targets) !== null && _a !== void 0 ? _a : {});
}
exports.getTargetFrameworksFromProjectAssetsJson = getTargetFrameworksFromProjectAssetsJson;
// Children of the root <Project> element with the given name, e.g. ItemGroup.
// Exported for use by lib/index.ts.
function getProjectChildren(manifestFile, name) {
return (0, case_insensitive_1.getAll)((0, case_insensitive_1.getCaseInsensitive)(manifestFile, 'Project'), name);
}
exports.getProjectChildren = getProjectChildren;
function hasTargetFrameworkProperty(propertyGroup) {
return ((0, case_insensitive_1.hasKey)(propertyGroup, 'TargetFramework') ||
(0, case_insensitive_1.hasKey)(propertyGroup, 'TargetFrameworks') ||
(0, case_insensitive_1.hasKey)(propertyGroup, 'TargetFrameworkVersion'));
}
//# sourceMappingURL=index.js.map