-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: add support for decorator metadata in scope analysis and in consistent-type-imports #2751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thanks for the PR, @Frezc! typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community. The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately. Thanks again! 🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day. |
Codecov Report
@@ Coverage Diff @@
## master #2751 +/- ##
==========================================
- Coverage 92.76% 92.68% -0.08%
==========================================
Files 310 312 +2
Lines 10393 10584 +191
Branches 2943 3004 +61
==========================================
+ Hits 9641 9810 +169
- Misses 347 353 +6
- Partials 405 421 +16
Flags with carried forward coverage won't be shown. Click here to find out more.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking into this!
Quick eyeball of the code - a few comments:
requiring type information in the rule is a breaking change.
We should not be making this change as it hugely limits the usability of the rule.
This is the wrong approach to fix this - this should be fixed in the scope analyser - not in the rule.
Fixing this in the scope analyser would benefit all consumers of scope information, and not just this specific rule.
I set
emm.. I have no idea what is scope analyser. Can you give me a brief explanation? |
The scope analyser is the tooling which runs over the AST and figures out what's a variable and where it is used. Right now it doesn't understand that the decorators can consume values when So when it analyses constructor(
private readonly config: ConfigService,
private readonly usersService: UsersService,
private readonly logger: Logger,
private readonly authService: AuthService,
@InjectRepository(UserSessionRepository)
private readonly userSessionRepository: UserSessionRepository,
) It doesn't understand that Because it doesn't understand this - it just creates a type-only reference to Fix the source of the data (the scope analyser) and the rule will "just work". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely heading in the right direction!
This is looking great so far!
Instead of intermingling all of this logic with Referencer
, I think we should separate it out into its own ClassVisitor
- similar to how we currently have TypeVisitor
and PatternVisitor
.
Classes are a pretty strict set of things, so we can more easily isolate the logic and setup the appropriate state there.
I mention this as there is one case that you haven't yet handled with your implementation code:
import Thing from 'thing';
@decorator
class Foo {
constructor(
arg: Thing,
) {}
}
In this example, the decorator on the class with a constructor creates a similar reference to that of a decorated method.
Handling this case will involve another piece of local state and things could get messy at that point, because you can nest class declarations within methods:
@decorator
class Bar {
constructor(
arg: React.Component,
) {
@decorator
class Foo {
constructor(
arg: React.Component,
) {}
}
}
}
So being able to do ClassVisitor.visit(clazz)
will be much nicer as we can construct one ClassVisitor per class and thus isolate the state entirely.
It's also worth noting that decorators are only ever valid for class declarations - so we could do some cleaner separation of logic to ignore bad cases:
@decorator
class Foo {
constructor(
arg: React.Component,
) {}
}
@decorator // this is invalid (TS semantic error)
const classExpr = class {
@decorator // this is invalid (TS semantic error)
method(
@decorator // this is invalid (TS semantic error)
arg: React.Component,
) {}
}
const obj = {
@decorator // this is invalid (syntax error)
method(
@decorator // this is invalid (TS semantic error)
arg: React.Component,
) {}
}
One other case I don't see tested, is this:
class Foo {
method(
@decorator
arg1: Thing1,
arg2: Thing2,
) {}
}
In this instance - TS will emit metadata about both Thing1
and Thing2
, even though only arg1
is decorated.
All of the cases that I found TS to emit metadata are listed in my comment here: #2559 (comment)
I missed this, thanks for hint. |
One thing I didn't mention - please add some snapshot tests! Your tests so far are great as they are explicit assertions of how it works, but as you've probably found out they're super cumbersome to write. A snapshot test is less strict/safe as you have to manually eyeball the output, but they're nice because they're so easy to write. It means you can create isolated cases for each case and have a granular regression test. They're also nice because they create a visual representation of the scope, which I found invaluable in figuring out how it all works. You can add the new scope manager option here And then add this to the top of your fixture to turn on the option (example). //// @emitDecoratorMetadata = true
class Fixture {...} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! The code's looking good I think.
could you also add some snapshots for the nested class cases:
function deco(...param: any) {}
@deco
class A {
constructor(foo: b.Foo) {
class B {
constructor(bar: c.Foo) {}
}
}
}
function deco(...param: any) {}
class A {
constructor(foo: b.Foo) {
@deco
class B {
constructor(bar: c.Foo) {}
}
}
}
function deco(...param: any) {}
@deco
class A {
constructor(foo: b.Foo) {
@deco
class B {
constructor(bar: c.Foo) {}
}
}
}
They should all work fine - but a few extra snapshots to prevent regressions would be ace.
Thanks for sticking with this! We're almost there!
packages/scope-manager/tests/fixtures/class/declaration/emit-metadata.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - thanks heaps for all of your work here and following this through to the end.
You've done a great job!
packages/scope-manager/tests/fixtures/class/emit-metadata/method-deco.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-both.ts.shot
Show resolved
Hide resolved
Reference$9 { | ||
identifier: Identifier<"T">, | ||
isRead: true, | ||
isTypeReference: true, | ||
isValueReference: false, | ||
isWrite: false, | ||
resolved: Variable$5, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the looks of it - this one is wrong.
This reference is for the pair:
set [keyName](a: T) {}
@deco
get [keyName]() {}
TS will emit the following code:
__decorate([
deco,
__metadata("design:type", T),
__metadata("design:paramtypes", [])
], A.prototype, _a, null);
So whilst it hasn't referenced the param type - it did reference the return type.
I don't think it's worth spending any time fixing this - it's a super rare usecase. Also it'll be a pretty hard case to reliably fix I think.
I'm 100% okay with just marking this as a KP and only looking at fixing it if someone runs into the issue.
packages/scope-manager/tests/fixtures/class/emit-metadata/accessor-deco.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-inner.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/nested-class-outer.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/parameters-deco.ts.shot
Show resolved
Hide resolved
packages/scope-manager/tests/fixtures/class/emit-metadata/property-deco.ts.shot
Show resolved
Hide resolved
I think this change broke After updating
|
And as linked above, this was fixed in #2943 |
Don't comment on old, closed PRs. The comments are not easily discoverable by other users, and PRs do not show up in the "issues" tab - further hiding them from other users. There is a pinned issue to follow, which is easily discoverable by using the issue search for |
Fixes #2559
Changes: