-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
feat(swc): Add comment preservation #3815
Conversation
noop_visit_mut_type!(); | ||
|
||
fn visit_mut_span(&mut self, span: &mut Span) { | ||
if span.is_dummy() || self.is_first_span { |
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.
When rearranging comments, we have to stop and think what we should do with the module / script span. A comment at the top could be dropped, could be preserved in place, or could be transported to the next available span. Example of this ambiguity:
On the one hand, I could have something like this:
/* Some doc block, this very clearly should remain at the top of the file */
type X = number;
...
Or I could have something like this:
/* istanbul ignore next-line */
type X = number;
const somethingFancy = (...x) => x;
Because during transpilation, code such as the second example will be shifted down to support utility functions, I opted to solve this ambiguity by assuming that the comment must be moved to the next nearest existing span.
This way at the very minimum tooling is supported.
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.
Code changes looks fine, but I think we need some tests to ensure that we does not use comments of other modules.
Can you add some tests js test suite at https://github.com/swc-project/swc/tree/ce78344a7c3597559ef32b0ee727dc0c7512c492/node-swc/__tests__ ?
Also, please fix the clippy errors.
@kdy1 happy to add the tests, will start early tomorrow pst. Could you help me understand what you mean by "use comments of other modules"? Lets say I call I'd expect to see all comments within I'll also update the node-swc config types in the follow-up commit. Thanks! |
SWC has a global storage for comments, and the storage is shared among files. And the storage uses This may change in the future, but for now, we have to ensure that |
crates/swc/src/config/mod.rs
Outdated
@@ -1021,6 +1028,9 @@ pub struct JscConfig { | |||
|
|||
#[serde(default)] | |||
pub lints: LintConfig, | |||
|
|||
#[serde(default)] | |||
pub preserve_dropped_comments: bool, |
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.
is it necessary?
I suppose swc should always preserve comments.
And It's ok to remove comments at the minify phase.
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.
It's because this patch tries to preserve comments on dropped nodes.
Btw, I think preserve_all_comments
is better name. (Even if it does not preserve all comments)
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.
@magic-akari I added this because it was requested here: #2964 (comment)
986242e
to
8b971af
Compare
@kdy1 lmk if this is what you had in mind: https://github.com/swc-project/swc/blob/8b971afa46f9359b7b7ac8fa5d843b35f3de4dd0/node-swc/__tests__/preserve_comments.mjs Clippy errors fixed, unmarking this as a draft and requesting another pass. thanks! |
Yes, that's what I meant. |
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.
I'm not sure about the way to fix this, so I'll think about it more.
let mut leading_comments = Vec::new(); | ||
let mut trailing_comments = Vec::new(); | ||
|
||
for idx in (self.comment_position..=span.lo.0).map(BytePos) { |
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.
Oh, this will be extremely slow.
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.
It removes whole advantage of storing comments in a shared hash map.
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.
@kdy1 Sorry, it was an extremely busy day for me. I'm just now sitting down and thinking about how to make this more this more performant.
Reviewing the discord convo from last night, your suggestion to use SingleThreadedComments
is unclear to me. I don't see a way to depart from the SwcComments
data structure that is used top-level in Compiler
without replacing the compiler type, because this needs to be implemented as a pass baked into existing compiler.
Instead, I think we need to pull hashing operations out of the visitor entirely.
This seems to be a more appropriate way to solve this problem:
- Collect all candidate spans with a visitor
- iterate exactly once over the
comments
entries, shifting orphaned comments to candidate spans
I understand the reasons that iterating over the concurrent hashmaps is undesirable, but reducing that iteration to one time outside of the visitor sounds like an acceptable concession. Do you agree?
If not, could you provide some guidance on how exactly you intend single threaded comments to be used.
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.
because this needs to be implemented as a pass baked into existing compiler
No, it should not and it does not have to.
It will used inside fn transform
and it will be dropped once fn transform()
is finished.
8b971af
to
891a316
Compare
@kdy1 addressed the performance concerns you had by reworking the approach to comment shifting. Also followed the general feedback from our discord chat. The summary of the comment shifting refactor is:
Otherwise, moved Unmarking as draft and requesting another pass, thanks a ton! |
891a316
to
bcd52f0
Compare
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.
Thank you!
let (comments_to_move, next_byte_positions): (CommentEntries, CommentEntries) = | ||
existing_comments | ||
.drain(..) | ||
.partition(|(bp, _)| bp <= &span.lo); |
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.
Please use *bp <= span.lo
instead
/// comments. | ||
|
||
pub fn dropped_comments_preserver( | ||
comments: Option<&SingleThreadedComments>, |
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.
I think we don't need a lifetime here.
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.
Comments
are cheap to clone, and it's part of the api.
/// similar location | ||
fn shift_trailing_comments(&mut self, remaining_comment_entries: CommentEntries) { | ||
let last_trailing = | ||
self.known_spans.iter().fold( |
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.
Please use .iter().copied()
instead.
Span
is Copy
, so we don't need indirection here.
Feedback addressed 7b3d8e3 |
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.
Thank you!
swc-bump:
- swc --breaking
Docs update following swc-project/swc#3815 Will be valid post `v1.2.153` milestone release
Docs update following swc-project/swc#3815 Will be valid post `v1.2.153` milestone release
Docs update following swc-project/swc#3815 Will be valid post `v1.2.153` milestone release
Problem
Generated code will drop comments in a variety of situations. Some of these situations may include:
The JS Ecosystem is (unfortunately) reliant on maintaining comment continuity between source and compiled code. There are many existing tools that use comments as annotations for various functionality, the most entrenched probably being
istanbul
coverage annotations. Not maintaining good parity between source and compiled output causes adopting SWC for test runners to be non-tenable for a lot of customers.Solution
Implement a "comment fixer" visitor,
dropped_comments_preserver
, and introduce a feature flagpreserve_dropped_comments
into the jsc configuration object.Overview of changes
Given the way SWC internals track comments, the comment fixer makes a best effort to preserve comments that may have otherwise been dropped. The output may not be 1:1 with what babel or other transpilers output, but it will preserve all expected functionality from existing js tooling where possible.
When
preserve_dropped_comments
is enabled, all trailing and leading comments will be shifted to the first non-dummy span that exists in the ast. This guarantees that all existing comments will be transported to the compiled output, orphaned comments will precede comments that would otherwise be preserved, and comments that would be preserved remain in a consistent position.I tried my best to organize things as I thought they should be according to the current standards of the repo. Open to any and all suggestions 🙏 would really like to support this feature as it is currently blocking us over here at Walmart from adopting the SWC jest transformer fully. The gains are too good to ignore.
BREAKING CHANGE:
**Related issue (if exists): #2964 **
Closes #2964