Skip to content

Commit

Permalink
fix(core): match on full segments instead of substrings
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo committed Jan 9, 2025
1 parent 054db2e commit 90c8349
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 31 deletions.
37 changes: 24 additions & 13 deletions e2e/nx/src/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,37 @@ describe('Nx Running Tests', () => {
});
});

it('should support running using a substring of the project name', () => {
// Note: actual project name will have some numbers at the end.
expect(() => runCLI(`echo proj`)).not.toThrow();
});

it('should error when multiple projects match a substring', () => {
const test1 = uniq('test1');
const test2 = uniq('test2');
runCLI(`generate @nx/js:lib libs/${test1}`);
runCLI(`generate @nx/js:lib libs/${test2}`);
updateJson(`libs/${test1}/project.json`, (c) => {
it('should support running with simple names (i.e. matching on full segments)', () => {
const foo = uniq('foo');
const bar = uniq('bar');
const nested = uniq('nested');
runCLI(`generate @nx/js:lib libs/${foo}`);
runCLI(`generate @nx/js:lib libs/${bar}`);
runCLI(`generate @nx/js:lib libs/nested/${nested}`);
updateJson(`libs/${foo}/project.json`, (c) => {
c.name = `@acme/${foo}`;
c.targets['echo'] = { command: 'echo TEST' };
return c;
});
updateJson(`libs/${test2}/project.json`, (c) => {
updateJson(`libs/${bar}/project.json`, (c) => {
c.name = `@acme/${bar}`;
c.targets['echo'] = { command: 'echo TEST' };
return c;
});
updateJson(`libs/nested/${nested}/project.json`, (c) => {
c.name = `@acme/nested/${bar}`; // The last segment is a duplicate
c.targets['echo'] = { command: 'echo TEST' };
return c;
});

// Full segments should match
expect(() => runCLI(`echo ${foo}`)).not.toThrow();

// Multiple matches should fail
expect(() => runCLI(`echo ${bar}`)).toThrow();

expect(() => runCLI(`echo test`)).toThrow();
// Partial segments should not match (Note: project foo has numbers in the end that aren't matched fully)
expect(() => runCLI(`echo foo`)).toThrow();
});

it.each([
Expand Down
86 changes: 80 additions & 6 deletions packages/nx/src/utils/find-matching-projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,39 @@ describe('findMatchingProjects', () => {
tags: [],
},
},
'@acme/foo': {
name: '@acme/foo',
type: 'lib',
data: {
root: 'lib/foo',
tags: [],
},
},
'@acme/bar': {
name: '@acme/bar',
type: 'lib',
data: {
root: 'lib/bar',
tags: [],
},
},
'@acme/foobar': {
name: '@acme/foobar',
type: 'lib',
data: {
root: 'lib/foobar',
tags: [],
},
},
// Technically, this isn't a valid npm package name, but we can handle it anyway just in case.
'@acme/nested/foo': {
name: '@acme/nested/foo',
type: 'lib',
data: {
root: 'lib/nested/foo',
tags: [],
},
},
};

it('should return no projects when passed no patterns', () => {
Expand All @@ -68,6 +101,10 @@ describe('findMatchingProjects', () => {
'b',
'c',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
]);
});

Expand All @@ -77,6 +114,10 @@ describe('findMatchingProjects', () => {
'b',
'c',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
]);
expect(findMatchingProjects(['a', '!*'], projectGraph)).toEqual([]);
});
Expand Down Expand Up @@ -120,6 +161,10 @@ describe('findMatchingProjects', () => {
'b',
'c',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
]);
});

Expand All @@ -131,7 +176,7 @@ describe('findMatchingProjects', () => {
projectGraph
);
expect(matches).toEqual(expect.arrayContaining(['a', 'b', 'nested']));
expect(matches.length).toEqual(3);
expect(matches.length).toEqual(7);
});

it('should expand generic glob patterns for tags', () => {
Expand All @@ -156,6 +201,9 @@ describe('findMatchingProjects', () => {
'test-project',
'a',
'b',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
]);
expect(findMatchingProjects(['apps/*'], projectGraph)).toEqual(['c']);
expect(findMatchingProjects(['**/nested'], projectGraph)).toEqual([
Expand All @@ -169,21 +217,47 @@ describe('findMatchingProjects', () => {
'b',
'c',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
]);
expect(findMatchingProjects(['!tag:api'], projectGraph)).toEqual([
'b',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
]);
expect(
findMatchingProjects(['!tag:api', 'test-project'], projectGraph)
).toEqual(['b', 'nested', 'test-project']);
).toEqual([
'b',
'nested',
'@acme/foo',
'@acme/bar',
'@acme/foobar',
'@acme/nested/foo',
'test-project',
]);
});

it('should match on substring of names', () => {
expect(findMatchingProjects(['test', 'nest'], projectGraph)).toEqual([
'test-project',
'nested',
it('should match on name segments', () => {
expect(findMatchingProjects(['foo'], projectGraph)).toEqual([
'@acme/foo',
'@acme/nested/foo',
]);
expect(findMatchingProjects(['bar'], projectGraph)).toEqual(['@acme/bar']);
expect(findMatchingProjects(['foobar'], projectGraph)).toEqual([
'@acme/foobar',
]);
expect(findMatchingProjects(['nested/foo'], projectGraph)).toEqual([
'@acme/nested/foo',
]);
// Only full segments are matched
expect(findMatchingProjects(['fo'], projectGraph)).toEqual([]);
expect(findMatchingProjects(['nested/fo'], projectGraph)).toEqual([]);
});
});

Expand Down
21 changes: 9 additions & 12 deletions packages/nx/src/utils/find-matching-projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,15 @@ function addMatchingProjectsByName(
}

if (!isGlobPattern(pattern.value)) {
// Only matching substrings when string is long enough, otherwise there will be too many matches.
// This is consistent with the behavior of run-one.ts.
if (pattern.value.length > 1) {
const matchingProjects = Object.keys(projects).filter((name) =>
name.includes(pattern.value)
);
for (const projectName of matchingProjects) {
if (pattern.exclude) {
matchedProjects.delete(projectName);
} else {
matchedProjects.add(projectName);
}
const regex = new RegExp(`\\b${pattern.value}\\b`);
const matchingProjects = Object.keys(projects).filter((name) =>
regex.test(name)
);
for (const projectName of matchingProjects) {
if (pattern.exclude) {
matchedProjects.delete(projectName);
} else {
matchedProjects.add(projectName);
}
}
return;
Expand Down

0 comments on commit 90c8349

Please sign in to comment.