Skip to content

Conversation

@Austaras
Copy link
Member

@Austaras Austaras commented Oct 28, 2025

Description:

图片

BREAKING CHANGE:

Related issue (if exists):
Closes #11167

Copilot AI review requested due to automatic review settings October 28, 2025 11:11
@Austaras Austaras requested a review from a team as a code owner October 28, 2025 11:11
@changeset-bot
Copy link

changeset-bot bot commented Oct 28, 2025

🦋 Changeset detected

Latest commit: f8671a2

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses an issue where the parser incorrectly positioned the program start, causing leading comments and JSDoc annotations to be misplaced after import statements in the generated output. The fix ensures that programs start at the actual beginning of the input, preserving the correct order of comments and code.

Key changes:

  • Modified parser to correctly track program start position at input beginning
  • Fixed comment positioning for files with leading comments before imports/code
  • Updated test fixtures to reflect correct comment placement

Reviewed Changes

Copilot reviewed 293 out of 644 changed files in this pull request and generated no comments.

File Description
Multiple test reference files (.1.normal.js) Updated test outputs showing comments now correctly appear before imports instead of after
Test fixture output files Fixed comment positioning in various scenarios including JSDoc, copyright notices, and inline comments
New test case files (crates/swc/tests/fixture/issues-110xx/11167/) Added new test case demonstrating the fix for issue #11167

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Austaras
Copy link
Member Author

Some investigation

For code

// 123
class A extends B {

}

Current swc would output

// 123
import { _ as _call_super } from "@swc/helpers/_/_call_super";
import { _ as _class_call_check } from "@swc/helpers/_/_class_call_check";
import { _ as _inherits } from "@swc/helpers/_/_inherits";
var A = /*#__PURE__*/ function(B1) {
    "use strict";
    _inherits(A, B1);
    function A() {
        _class_call_check(this, A);
        return _call_super(this, A, arguments);
    }
    return A;
}(B);

tsc would output

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
// 123
var A = /** @class */ (function (_super) {
    __extends(A, _super);
    function A() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return A;
}(B));

babel would output

function _callSuper(t, o, e) { return o = babelHelpers.getPrototypeOf(o), babelHelpers.possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], babelHelpers.getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
// 123
var A = /*#__PURE__*/function (_B) {
  function A() {
    babelHelpers.classCallCheck(this, A);
    return _callSuper(this, A, arguments);
  }
  babelHelpers.inherits(A, _B);
  return babelHelpers.createClass(A);
}(B);

This PR would change to babel output.

@Austaras
Copy link
Member Author

Some comments would be lost because they're now attached to typescript syntax node which would be eliminated later.

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 28, 2025

CodSpeed Performance Report

Merging #11199 will not alter performance

Comparing Austaras:main (f8671a2) with main (4457f89)1

Summary

✅ 138 untouched

Footnotes

  1. No successful run was found on main (c95ffa8) during the generation of this report, so 4457f89 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copilot AI review requested due to automatic review settings October 28, 2025 11:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4003 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings October 29, 2025 12:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4007 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kdy1 kdy1 added this to the Planned milestone Oct 29, 2025
Copilot AI review requested due to automatic review settings October 30, 2025 04:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Binary Sizes

File Size
swc.linux-x64-gnu.node 31M (31906504 bytes)

Commit: e929313

@kdy1
Copy link
Member

kdy1 commented Oct 31, 2025

CI failures look like a real failure, but I think it may be better to wait until I finish #11205

Copilot AI review requested due to automatic review settings November 1, 2025 09:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4021 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings November 2, 2025 17:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4022 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

/*
@internal
*/ declare class StripInternalClassFields {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should be removed.

@kdy1
Copy link
Member

kdy1 commented Nov 5, 2025

I started reviewing this. Sorry for the late review.

@kdy1
Copy link
Member

kdy1 commented Nov 10, 2025

@claude Review this PR

Copilot AI review requested due to automatic review settings November 17, 2025 10:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4018 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Austaras Austaras requested a review from kdy1 November 17, 2025 11:32
@kdy1
Copy link
Member

kdy1 commented Nov 17, 2025

I'll merge after fixing CI with #11277

@kdy1
Copy link
Member

kdy1 commented Nov 17, 2025

@claude Review this PR. Look at only *.rs files.

@claude
Copy link
Contributor

claude bot commented Nov 17, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

1 similar comment
@claude
Copy link
Contributor

claude bot commented Nov 17, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@kdy1
Copy link
Member

kdy1 commented Nov 17, 2025

@claude Review this PR. Look at only *.rs files.

@claude
Copy link
Contributor

claude bot commented Nov 17, 2025

Claude finished @kdy1's task —— View job


Code Review: Rust Files Only ✅

Summary

Reviewed 4 Rust files in this PR that fix a fundamental parser positioning issue where program spans incorrectly started at the first token rather than input beginning, causing leading comments to appear after imports instead of before them.


File-by-File Analysis

crates/swc_ecma_parser/src/parser/input.rs

Changes:

  • Added start: BytePos field to Buffer<I> struct (line 77)
  • Initialized start in Buffer::new() to prev_span.lo (line 209)

Assessment: EXCELLENT

Strengths:

  • Clean, minimal change that captures the input start position
  • Proper initialization from start_pos() via prev_span.lo
  • Zero performance overhead (just stores a single BytePos)
  • Follows Rust best practices with public field visibility

Adherence to CLAUDE.md:

Suggested Improvement:

  • Could add a doc comment explaining why start is needed:
    /// The actual start position of the input, used to ensure program spans
    /// include all leading content (whitespace, comments, etc.)
    pub start: BytePos,

crates/swc_ecma_parser/src/parser/mod.rs

Changes:
Changed let start = self.cur_pos() to let start = self.input.start in 4 parsing methods:

  • parse_script() (line 145)
  • parse_commonjs() (line 170)
  • parse_program() (line 218)
  • parse_module() (line 277)

Assessment: EXCELLENT

Strengths:

  • Consistent change across all program-level parsing entry points
  • Semantically correct: program spans should start at input beginning
  • No logic changes beyond the span start position
  • All methods correctly use self.span(start) to create final spans

Correctness Analysis:

// Before (INCORRECT):
let start = self.cur_pos();  // Points to first token
let shebang = self.parse_shebang()?;
// Leading comments before first token are excluded from span

// After (CORRECT):
let start = self.input.start;  // Points to input beginning
let shebang = self.parse_shebang()?;
// Leading comments are included in span

Adherence to CLAUDE.md:

Suggested Improvement:

  • Consider adding a doc comment to these methods explaining the span behavior:
    /// Parse a module.
    ///
    /// The resulting module span starts at the beginning of the input
    /// (including any leading whitespace, comments, and shebang) rather
    /// than at the first token.
    pub fn parse_module(&mut self) -> PResult<Module> {

crates/swc_ecma_minifier/src/lib.rs

Changes:

  • Added import of Spanned trait (line 43)
  • Added comment preservation logic after DCE (lines 162-192)
    • Captures first statement span before DCE
    • Captures first statement span after DCE
    • If they differ, relocates leading comments to new first statement

Assessment: GOOD with minor concerns

Strengths:

  • Correctly addresses the minifier comment loss issue mentioned in review comments
  • Handles both Program::Module and Program::Script cases
  • Uses proper comment API (take_leading, add_leading)
  • Fallback to n.span().lo when no statements remain

Logic Analysis:

if first_span.map(|s| s.lo) != new_first_span.map(|s| s.lo) {
    if let (Some(span), Some(comments)) = (first_span, comments) {
        let comment = comments.take_leading(span.lo);
        let new_pos = new_first_span.unwrap_or(n.span()).lo;
        for cmt in comment.into_iter().flatten() {
            comments.add_leading(new_pos, cmt);
        }
    }
}

Concerns:

  1. Incomplete Solution: This only handles comments attached to the first statement. What about:

    • Comments attached to statements in the middle that get removed?
    • Trailing comments?
    • Comments between statements?
  2. Performance (CLAUDE.md ecmascript parser #1):

    • Iterates through body twice (before and after DCE) to get first span
    • take_leading() and add_leading() are relatively expensive
    • Only triggered when first statement changes, so impact is minimal in practice
  3. Edge Case: The unwrap_or(n.span()) fallback when all statements are removed might not be semantically correct - should we preserve comments when the entire program body is eliminated?

  4. No Documentation (CLAUDE.md Clean up #2, Travis and codecov #7): This complex logic lacks comments explaining:

    • Why we're relocating comments
    • What happens to other comments
    • The relationship to the parser fix

Suggested Improvement:

// Preserve leading comments when DCE removes the first statement.
// After the parser fix, leading comments are attached to the program span,
// but DCE may remove the first statement, leaving comments orphaned.
// We relocate them to the new first statement to prevent loss.
let first_span = match &n {
    Program::Module(module) => module.body.first().map(|s| s.span()),
    Program::Script(script) => script.body.first().map(|s| s.span()),
};

Adherence to CLAUDE.md:


crates/swc_ecma_transforms_typescript/src/typescript.rs

Changes:
Simplified span calculation by removing special case for shebang:

// Before:
let span = if n.shebang.is_some() {
    n.span.with_lo(n.body.first().map(|s| s.span_lo()).unwrap_or(n.span.lo))
} else {
    n.span
};

// After:
let span = n
    .span
    .with_lo(n.body.first().map(|s| s.span_lo()).unwrap_or(n.span.lo));

Assessment: EXCELLENT

Strengths:

  • Simplifies code by removing special case handling
  • Correct because n.span now starts at input beginning (includes shebang)
  • No logic change in the actual JSX directive parsing

Correctness:
Previously, the code special-cased shebang to adjust the span start. With the parser fix, n.span already starts at the input beginning (which may include shebang), so the special case is unnecessary. The code now consistently adjusts span to start at first body element.

Adherence to CLAUDE.md:


Overall Assessment

Score: 8.5/10 ⭐⭐⭐⭐½

Strengths

  1. Correctness: The core parser fix is semantically correct and addresses a fundamental issue
  2. Minimal Impact: Only 4 lines changed in core parser logic
  3. Performance: Zero performance overhead (CLAUDE.md ecmascript parser #1)
  4. Consistency: All parsing entry points updated consistently
  5. Test Coverage: Test fixture demonstrates the fix works
  6. Standards Alignment: Brings SWC behavior in line with TSC and Babel

Areas for Improvement

  1. ⚠️ Documentation (CLAUDE.md Clean up #2, Travis and codecov #7): The parser changes lack doc comments explaining the span behavior
  2. ⚠️ Minifier Logic Documentation (CLAUDE.md Clean up #2): Complex comment preservation logic needs explanatory comments
  3. ⚠️ Incomplete Comment Preservation: Minifier only handles leading comments on first statement
  4. ⚠️ Unit Tests (CLAUDE.md Handle early errors in ecmascript parser #5): No dedicated unit tests in parser module, only integration test fixtures
  5. ⚠️ License Comment Issue: As noted in review comments, the lodash.js @license comment is still being removed

Recommendations

Must Address

  1. Add documentation comments to the parser methods explaining that program spans start at input beginning:

    /// Parse a module.
    ///
    /// The resulting module span starts at the beginning of the input
    /// (including any leading whitespace, comments, and shebang) rather
    /// than at the first token.
    pub fn parse_module(&mut self) -> PResult<Module> {
  2. Document the minifier comment logic explaining why comments are relocated

Should Address

  1. Enhance minifier comment preservation to handle @license and @copyright comments specially, as mentioned in previous review
  2. Add unit tests in the parser module to test span behavior directly

Nice to Have

  1. Consider adding a doc comment to the Buffer::start field explaining its purpose

Verdict

APPROVE with minor documentation improvements needed

This is a well-executed fix for a legitimate parser issue. The Rust code is clean, performant, and correct. The main deficiency is lack of documentation for complex logic, particularly the minifier changes. The license comment preservation issue should be addressed in a follow-up PR as previously discussed.

The changes adhere well to most CLAUDE.md guidelines, particularly around performance (#1) and avoiding unstable features (#3). Adding documentation (#2, #7) and unit tests (#5) would bring it to full compliance.


Copy link
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Copy link
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Copilot AI review requested due to automatic review settings November 17, 2025 15:48
@kdy1 kdy1 requested a review from a team as a code owner November 17, 2025 15:48
@kdy1 kdy1 changed the title mod(es/parser): Program start at input start mod(es/parser): Make the span of Program start at input start Nov 17, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 293 out of 4019 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kdy1 kdy1 enabled auto-merge (squash) November 17, 2025 15:49
@kdy1 kdy1 disabled auto-merge November 17, 2025 16:09
@kdy1 kdy1 merged commit b56e008 into swc-project:main Nov 17, 2025
183 of 184 checks passed
claude bot pushed a commit that referenced this pull request Nov 18, 2025
This fixes an issue where top-level comments (like copyright headers)
were being lost after PR #11199 changed Module/Script spans to start
at position 0.

The problem was that Module::emit() was taking and emitting all leading
comments before any body items were processed. When helper imports were
injected at the beginning, comments would either appear before them
(wrong) or be lost entirely.

The fix is to skip emitting leading comments at Module/Script level when
the body is non-empty. Instead, comments are emitted by the first body
item with a real source span. Injected imports have DUMMY_SP, so they
skip comment emission, and the original source items emit their comments
correctly after the imports.

This ensures:
- Comments appear after injected imports (maintains fix for #11167)
- Comments are not lost (fixes regression from PR #11199)
- Module span still starts at position 0 (maintains PR #11199 changes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Donny/강동윤 <[email protected]>
kdy1 added a commit that referenced this pull request Nov 18, 2025
kdy1 added a commit that referenced this pull request Nov 19, 2025
This fixes an issue where top-level comments (like copyright headers)
were being lost after PR #11199 changed Module/Script spans to start at
position 0.

The fix skips emitting leading comments at Module/Script level when the
body is non-empty. Instead, comments are emitted by the first body item
with a real source span. Injected imports have DUMMY_SP, so they skip
comment emission, and original source items emit their comments
correctly after the imports.

Closes #11167

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Donny/강동윤 <[email protected]>
@kdy1 kdy1 modified the milestones: Planned, 1.15.3 Nov 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Top-level comments should be placed after @swc/helpers imports

2 participants