Skip to content

Bring back SourceFile.EndOfFileToken #1257

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

Conversation

Andarist
Copy link
Contributor

No description provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tbh, I don't know how to assess if this is a correct change or not

if len(p.reparseList) > 0 {
nodes = append(nodes, p.reparseList...)
p.reparseList = nil
end = p.nodePos()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not entirely sure if this dance with end is fully correct here. It seems to me that end for the statement list should get shifted if some reparsed nodes were pushed to it - otherwise some weird data mismatches would occur and the code would have to, likely, handle them in a special way. cc @sandersn

Copy link
Member

Choose a reason for hiding this comment

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

At first I thought you were wrong but after I tried replacing the mutation with a a panic I see you are right. I think it's the best option but it still might cause some weirdness when getTokenAtPosition should return an EOF. However, the reparsed nodes are marked as such and should be skipped by that code.

Copy link
Member

Choose a reason for hiding this comment

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

end should be a real location, so I would very much assume that we don't need to do anything here. The end of file token should always come after the statement list, and IIRC any reparsed nodes would have to as well, and so nothing special needs to happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, all of that makes sense and matches my expectations. I believe one of the previous iterations was not correct because it computed end before the reparsed nodes on the eof were handled. That's why I have changed the parseListIndex's return type - so I could get control over the assigned end position.

I've just done this because it intuitively made sense but I have not fully verified why the previous version of this code I had have not worked. And because of that, I just want the reviewers to be extra cautious here - even though I think this makes sense :)

Copy link
Member

Choose a reason for hiding this comment

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

I actually more confused as to why we need to append reparseList at all. I thought that was an internal detail of the reparser?

Copy link
Member

Choose a reason for hiding this comment

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

Hm, I guess that's where it sticks its statements, so.

Copy link
Member

@jakebailey jakebailey Jun 23, 2025

Choose a reason for hiding this comment

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

In other words, I think what you've written here is actually all correct.

Or at least, at worst maybe this position should just be the old end before the EOF.

@Andarist Andarist marked this pull request as ready for review June 22, 2025 10:02
@sandersn sandersn self-requested a review June 23, 2025 13:54
@sandersn sandersn self-assigned this Jun 23, 2025
Comment on lines +128 to +130
+ export default _default;
+ export type default = string | number;
Copy link
Member

Choose a reason for hiding this comment

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

Hm, this looks like a syntax error. Curious whether or not this is a quick fix. What should have happened here is that the type was renamed to _default too. But, that wouldn't be in the parser, but the declaration transform.

Copy link
Contributor Author

@Andarist Andarist Jun 23, 2025

Choose a reason for hiding this comment

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

I'll look into it. Btw. this doesn't correctly generate a unique identifier in Strada (:

// @allowJs: true
// @checkJs: true
// @target: es5
// @outDir: ./out
// @declaration: true

export const _default = class {}
export default 12;
/**
 * @typedef {string | number} default
 */

Copy link
Member

Choose a reason for hiding this comment

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

Playground Link is what I was testing; putting your code into that also seems to work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my sample, there is an explicit _default exported and the merged default export is assigned a _default name too. Both of those should refer to different identifiers/exports

==== /foo.js (3 errors) ====
/** @import * as f from "./foo" with */
+ ~~~~~~~
+!!! error TS2306: File '/foo.js' is not a module.
Copy link
Member

Choose a reason for hiding this comment

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

Weird, but we also have other follow-on errors anyway.

+++ new.jsDeclarationsFunctionPrototypeStatic.errors.txt
@@= skipped -0, +0 lines =@@
-<no content>
+source.js(1,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
Copy link
Member

Choose a reason for hiding this comment

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

This seems preexisting; the reparsed code triggers some other errors about addtional exports.

Copy link
Member

Choose a reason for hiding this comment

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

yep. I need to get back to the PR that fixes these now that declaration emit is done, and I'm done adding (most) JS features.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Still reviewing baselines, but here are some out of order comments.

@@ -49,6 +49,9 @@ func getTokenAtPosition(
left := 0

testNode := func(node *ast.Node) int {
if node.Kind == ast.KindEndOfFile {
return 0
Copy link
Member

Choose a reason for hiding this comment

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

how does this translate from the code in services/utilities.ts? I don't see a specific EOF case in that code so I assume it's simplified from it in some way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good question. It's an indirect port of the EOF case checked by nodeContainsPosition. The related code in Corsa is rewritten sligthly so it's not easy to see this. There is a chance this second nodeContainsPosition call might have to be ported too... but, so far, I have not found a need for it (at least not in the case of EOF):

            // first element whose start position is before the input and whose end position is after or equal to the input
            if (nodeContainsPosition(children[middle], start, end)) {
                if (children[middle - 1]) {
                    // we want the _first_ element that contains the position, so left-recur if the prior node also contains the position
                    if (nodeContainsPosition(children[middle - 1])) {
                        return Comparison.GreaterThan;
                    }
                }
                return Comparison.EqualTo;
            }

if len(p.reparseList) > 0 {
nodes = append(nodes, p.reparseList...)
p.reparseList = nil
end = p.nodePos()
Copy link
Member

Choose a reason for hiding this comment

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

At first I thought you were wrong but after I tried replacing the mutation with a a panic I see you are right. I think it's the best option but it still might cause some weirdness when getTokenAtPosition should return an EOF. However, the reparsed nodes are marked as such and should be skipped by that code.

@@ -102,6 +106,9 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
case ast.KindJSDocImportTag:
importTag := tag.AsJSDocImportTag()
importClause := importTag.ImportClause
if importClause == nil {
Copy link
Member

Choose a reason for hiding this comment

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

I think ModuleSpecifier is actually the critical property to require non-nil, although an import tag that imports no identifiers is pointless, so keep checking importClause for nil too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure if I understand correctly. Have you meant I should adjust it to this?

if importClause == nil || importTag.ModuleSpecifier == nil  {
  break
}

@@ -1,3 +1,39 @@
〚Positions: [729, 730]〛
【TS: SourceFile [0, 9772)】
《Go: EndOfFileToken [9770, 9772)》
Copy link
Member

Choose a reason for hiding this comment

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

@andrewbranch I don't know how to read these baselines, but this could be the difference in GetTokenAtPosition that I was worried about.

Copy link
Member

Choose a reason for hiding this comment

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

Later: no, there's no jsdoc at the end; this is just a difference in GetTokenAtPosition or something similar between Strada and Corsa. Based on my reading of the diff--still not an expert--it looks like it is incorrect and Strada is correct.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Requesting changes based on my previous comments, since this batch is just guesses for why diffs have happened.

@@ -1,3 +1,39 @@
〚Positions: [729, 730]〛
【TS: SourceFile [0, 9772)】
《Go: EndOfFileToken [9770, 9772)》
Copy link
Member

Choose a reason for hiding this comment

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

Later: no, there's no jsdoc at the end; this is just a difference in GetTokenAtPosition or something similar between Strada and Corsa. Based on my reading of the diff--still not an expert--it looks like it is incorrect and Strada is correct.

+!!! error TS1268: An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.
Copy link
Member

Choose a reason for hiding this comment

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

this error seems more accurate than the Strada one, although it's missing the suggestion to use a mapped type.

+>(barts, tidus, noctis) => "cecil" : (barts: { fantasy: any; }, tidus: { heroes: any; }, noctis: { heroes: any; } & { fantasy: any; }) => "cecil"
Copy link
Member

Choose a reason for hiding this comment

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

this diff is because the type annotation is now a contextual type, not a type annotation that attaches to the arrow

+ ~~~~~~~~~~~~~~~~~~
+!!! error TS1141: String literal expected.
Copy link
Member

Choose a reason for hiding this comment

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

I don't know why this parse error is different, but it's not related to this PR

+++ new.jsDeclarationsFunctionPrototypeStatic.errors.txt
@@= skipped -0, +0 lines =@@
-<no content>
+source.js(1,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
Copy link
Member

Choose a reason for hiding this comment

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

yep. I need to get back to the PR that fixes these now that declaration emit is done, and I'm done adding (most) JS features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants