Skip to content

Commit

Permalink
feat(linter): allow globs in onlyDependOnLibsWithTags eslint-plugin c…
Browse files Browse the repository at this point in the history
…onfiguration option

add support for globs (in addition to existing string, regex, and * options)

to the onlyDependOnLibsWithTags configuration option for the eslint-plugin

closed nrwl#15264
  • Loading branch information
ericyd committed Jun 9, 2023
1 parent bf2c42c commit eb81d8d
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 5 deletions.
49 changes: 44 additions & 5 deletions docs/shared/core-features/enforce-project-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,31 @@ Next you should update your root lint configuration:

With these constraints in place, `scope:client` projects can only depend on other `scope:client` projects or on `scope:shared` projects. And `scope:admin` projects can only depend on other `scope:admin` projects or on `scope:shared` projects. So `scope:client` and `scope:admin` cannot depend on each other.

Projects without any tags cannot depend on any other projects. If you add the following, projects without any tags will be able to depend on any other project.
Projects without any tags cannot depend on any other projects, unless you allow all tags (see below).

If you try to violate the constraints, you will get an error when linting:

```shell
A project tagged with "scope:admin" can only depend on projects
tagged with "scoped:shared" or "scope:admin".
```

### Tag formats

- `string`: allow exact tags

Example: projects tagged with `scope:client` can only depend on projects tagged `scope:client`

```json
{
"sourceTag": "scope:client",
"onlyDependOnLibsWithTags": ["scope:client"]
}
```

- `*`: allow all tags

Example: projects with any tags (including untagged) can depend on any other project.

```json
{
Expand All @@ -140,9 +164,24 @@ Projects without any tags cannot depend on any other projects. If you add the fo
}
```

If you try to violate the constraints, you will get an error when linting:
- `regex`: allow tags matching the regular expression

```shell
A project tagged with "scope:admin" can only depend on projects
tagged with "scoped:shared" or "scope:admin".
Example: projects tagged with `scope:client` can depend on projects with a tag matching the regular expression `/^scope.*/`. In this case `scope:a`, `scope:b`, etc are all allowed tags for dependencies.

```json
{
"sourceTag": "scope:client",
"onlyDependOnLibsWithTags": ["/^scope.*/"]
}
```

- `glob`: allow tags matching the glob

Example: projects with a tag starting with `scope:` can depend on projects with a tag that starts with `scope:*`. In this case `scope:a`, `scope:b`, etc are all allowed tags for dependencies.

```json
{
"sourceTag": "scope:*",
"onlyDependOnLibsWithTags": ["scope:*"]
}
```
26 changes: 26 additions & 0 deletions packages/eslint-plugin/src/rules/enforce-module-boundaries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,32 @@ Violation detected in:
expect(failures.length).toEqual(0);
});

it('should support globs', () => {
const failures = runRule(
{
depConstraints: [
{
sourceTag: 'p*',
onlyDependOnLibsWithTags: ['domain*'],
},
],
},
`${process.cwd()}/proj/libs/public/src/index.ts`,
`
import '@mycompany/impl-domain2';
import('@mycompany/impl-domain2');
import '@mycompany/impl-both-domains';
import('@mycompany/impl-both-domains');
import '@mycompany/impl';
import('@mycompany/impl');
`,
graph,
fileMap
);

expect(failures.length).toEqual(0);
});

it('should report errors for combo source tags', () => {
const failures = runRule(
{
Expand Down
49 changes: 49 additions & 0 deletions packages/eslint-plugin/src/utils/runtime-lint-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
findTransitiveExternalDependencies,
hasBannedDependencies,
hasBannedImport,
hasNoneOfTheseTags,
isAngularSecondaryEntrypoint,
isTerminalRun,
} from './runtime-lint-utils';
Expand Down Expand Up @@ -80,6 +81,28 @@ describe('findConstraintsFor', () => {
})
).toEqual([{ sourceTag: '/a|b/', onlyDependOnLibsWithTags: ['c'] }]);
});

it('should find constraints matching glob', () => {
const constriants: DepConstraint[] = [
{ sourceTag: 'a:*', onlyDependOnLibsWithTags: ['b:*'] },
{ sourceTag: 'b:*', onlyDependOnLibsWithTags: ['c:*'] },
{ sourceTag: 'c:*', onlyDependOnLibsWithTags: ['a:*'] },
];
expect(
findConstraintsFor(constriants, {
type: 'lib',
name: 'someLib',
data: { root: '.', tags: ['a:a'] },
})
).toEqual([{ sourceTag: 'a:*', onlyDependOnLibsWithTags: ['b:*'] }]);
expect(
findConstraintsFor(constriants, {
type: 'lib',
name: 'someLib',
data: { root: '.', tags: ['a:abc'] },
})
).toEqual([{ sourceTag: 'a:*', onlyDependOnLibsWithTags: ['b:*'] }]);
});
});

describe('hasBannedImport', () => {
Expand Down Expand Up @@ -474,3 +497,29 @@ describe('isAngularSecondaryEntrypoint', () => {
).toBe(false);
});
});

describe('hasNoneOfTheseTags', () => {
const source: ProjectGraphProjectNode = {
type: 'lib',
name: 'aLib',
data: {
tags: ['abc'],
} as any,
};

it.each([
[true, ['a']],
[true, ['b']],
[true, ['c']],
[true, ['az*']],
[true, ['/[A-Z]+/']],
[false, ['ab*']],
[false, ['*']],
[false, ['/[a-z]*/']],
])(
'should return %s when project has tags ["abc"] and requested tags are %s',
(expected, tags) => {
expect(hasNoneOfTheseTags(source, tags)).toBe(expected);
}
);
});
6 changes: 6 additions & 0 deletions packages/eslint-plugin/src/utils/runtime-lint-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ function hasTag(proj: ProjectGraphProjectNode, tag: string): boolean {
return (proj.data.tags || []).some((t) => regex.test(t));
}

// if the tag is a glob, check if the project matches the glob prefix
if (tag.endsWith('*')) {
const prefix = tag.substring(0, tag.length - 1);
return (proj.data.tags || []).some((t) => t.startsWith(prefix));
}

return (proj.data.tags || []).indexOf(tag) > -1;
}

Expand Down

0 comments on commit eb81d8d

Please sign in to comment.