Skip to content

Commit

Permalink
feat(leiningen): support lein-parent (#29552)
Browse files Browse the repository at this point in the history
  • Loading branch information
MIJOTHY authored Jun 16, 2024
1 parent 51e33da commit c3bd354
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 8 deletions.
110 changes: 110 additions & 0 deletions lib/modules/manager/leiningen/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { codeBlock } from 'common-tags';
import { Fixtures } from '../../../../test/fixtures';
import { ClojureDatasource } from '../../datasource/clojure';
import { extractFromVectors, extractVariables, trimAtKey } from './extract';
Expand All @@ -10,6 +11,12 @@ describe('modules/manager/leiningen/extract', () => {
expect(trimAtKey('foo', 'bar')).toBeNull();
expect(trimAtKey(':dependencies ', 'dependencies')).toBeNull();
expect(trimAtKey(':dependencies \nfoobar', 'dependencies')).toBe('foobar');
expect(
trimAtKey(
':parent-project {:coords [my-org/my-parent "4.3.0"]\n:inherit [:profiles]}',
'coords',
),
).toBe('[my-org/my-parent "4.3.0"]\n:inherit [:profiles]}');
});

it('extractFromVectors', () => {
Expand Down Expand Up @@ -48,6 +55,20 @@ describe('modules/manager/leiningen/extract', () => {
currentValue: '4.5.6',
},
]);
expect(
extractFromVectors(
'[my-org/my-parent "4.3.0"]\n:inherit [:profiles]}',
{},
{},
1,
),
).toEqual([
{
datasource: ClojureDatasource.id,
depName: 'my-org:my-parent',
currentValue: '4.3.0',
},
]);
});

it('extractPackageFile', () => {
Expand Down Expand Up @@ -124,6 +145,95 @@ describe('modules/manager/leiningen/extract', () => {
},
],
});

const parentProjectSrc = codeBlock`
(defproject org.example/parent-project "1.0.0-SNAPSHOT"
:plugins [[lein-parent "0.3.9"]
[lein-project-version "0.1.0"]
[lein-shell "0.5.0"]]
:parent-project {:coords [my-org/my-parent "4.3.0"]
:inherit [:profiles :managed-dependencies :local-repo]}
:profiles {:cljfmt {:plugins [[lein-cljfmt "0.9.2"]]}}
:dependencies [[org.clojure/core.async "1.6.681"]
[org.clojure/core.match "1.1.0"]
[org.clojure/data.csv "1.1.0"]
[org.clojure/tools.cli "1.1.230"]
[metosin/malli "0.15.0"]])`;

expect(extractPackageFile(parentProjectSrc)).toMatchObject({
deps: [
{
depName: 'org.clojure:core.async',
datasource: 'clojure',
depType: 'dependencies',
registryUrls: [],
currentValue: '1.6.681',
},
{
depName: 'org.clojure:core.match',
datasource: 'clojure',
depType: 'dependencies',
registryUrls: [],
currentValue: '1.1.0',
},
{
depName: 'org.clojure:data.csv',
datasource: 'clojure',
depType: 'dependencies',
registryUrls: [],
currentValue: '1.1.0',
},
{
depName: 'org.clojure:tools.cli',
datasource: 'clojure',
depType: 'dependencies',
registryUrls: [],
currentValue: '1.1.230',
},
{
depName: 'metosin:malli',
datasource: 'clojure',
depType: 'dependencies',
registryUrls: [],
currentValue: '0.15.0',
},
{
depName: 'lein-parent:lein-parent',
datasource: 'clojure',
depType: 'plugins',
registryUrls: [],
currentValue: '0.3.9',
},
{
depName: 'lein-project-version:lein-project-version',
datasource: 'clojure',
depType: 'plugins',
registryUrls: [],
currentValue: '0.1.0',
},
{
depName: 'lein-shell:lein-shell',
datasource: 'clojure',
depType: 'plugins',
registryUrls: [],
currentValue: '0.5.0',
},
{
depName: 'lein-cljfmt:lein-cljfmt',
datasource: 'clojure',
depType: 'plugins',
registryUrls: [],
currentValue: '0.9.2',
},
{
depName: 'my-org:my-parent',
datasource: 'clojure',
depType: 'parent-project',
registryUrls: [],
currentValue: '4.3.0',
},
],
});
});

it('extractVariables', () => {
Expand Down
47 changes: 39 additions & 8 deletions lib/modules/manager/leiningen/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function extractFromVectors(
str: string,
ctx: ExtractContext = {},
vars: ExtractedVariables = {},
dimensions: 1 | 2 = 2,
): PackageDependency[] {
if (!str.startsWith('[')) {
return [];
Expand All @@ -36,7 +37,8 @@ export function extractFromVectors(
let vecPos = 0;
let artifactId = '';
let version = '';
let commentLevel = 0;
// Are we currently parsing a comment? If so, at what depth?
let commentLevel: number | null = null;

const isSpace = (ch: string | null): boolean =>
!!ch && regEx(/[\s,]/).test(ch);
Expand Down Expand Up @@ -82,7 +84,7 @@ export function extractFromVectors(

if (char === '[') {
balance += 1;
if (balance === 2) {
if (balance === dimensions) {
vecPos = 0;
}
} else if (char === ']') {
Expand All @@ -91,15 +93,17 @@ export function extractFromVectors(
if (commentLevel === balance) {
artifactId = '';
version = '';
commentLevel = 0;
commentLevel = null;
}

if (balance === 1) {
if (balance === dimensions - 1) {
yieldDep();
} else if (balance === 0) {
}

if (balance === 0) {
break;
}
} else if (balance === 2) {
} else if (balance === dimensions) {
if (isSpace(char)) {
if (!isSpace(prevChar)) {
vecPos += 1;
Expand Down Expand Up @@ -170,20 +174,34 @@ export function extractVariables(content: string): ExtractedVariables {
return result;
}

interface CollectDepsOptions {
nested: boolean;
depType?: string;
}

function collectDeps(
content: string,
key: string,
registryUrls: string[],
vars: ExtractedVariables,
options: CollectDepsOptions = {
nested: true,
},
): PackageDependency[] {
const ctx = {
depType: key,
depType: options.depType ?? key,
registryUrls,
};
// A vector like [["dep-1" "1.0.0"] ["dep-2" "0.0.0"]] is nested
// A vector like ["dep-1" "1.0.0"] is not
const dimensions = options.nested ? 2 : 1;
let result: PackageDependency[] = [];
let restContent = trimAtKey(content, key);
while (restContent) {
result = [...result, ...extractFromVectors(restContent, ctx, vars)];
result = [
...result,
...extractFromVectors(restContent, ctx, vars, dimensions),
];
restContent = trimAtKey(restContent, key);
}
return result;
Expand All @@ -198,6 +216,19 @@ export function extractPackageFile(content: string): PackageFileContent {
...collectDeps(content, 'managed-dependencies', registryUrls, vars),
...collectDeps(content, 'plugins', registryUrls, vars),
...collectDeps(content, 'pom-plugins', registryUrls, vars),
// 'coords' is used in lein parent, and specifies zero or one
// dependencies. These are not wrapped in a vector in the way other
// dependencies are. The project.clj fragment looks like
//
// :parent-project {... :coords ["parent" "version"] ...}
//
// - https://github.com/achin/lein-parent
...collectDeps(content, 'coords', registryUrls, vars, {
nested: false,
// The top-level key is 'parent-project', but we skip directly to 'coords'.
// So fix the dep type label
depType: 'parent-project',
}),
];

return { deps };
Expand Down

0 comments on commit c3bd354

Please sign in to comment.