Reparse non-identifier jsdoc names where possible, error otherwise#4058
Reparse non-identifier jsdoc names where possible, error otherwise#4058weswigham wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adjusts the JSDoc reparse logic so that non-identifier names from JSDoc tags either get rewritten into something emittable (string-literal property names for @property, sanitized identifier names for @param) or produce an Identifier expected diagnostic (e.g. on nameless @typedef, callback type-parameter names, function/method declaration names, type-parameter constraint heads). This fixes #4011, where property names with hyphens were emitted unquoted in .d.ts output.
Changes:
- Add
checkNonIdentifierNameto emitIdentifier expectedon invalid identifier names used in reparsed typedef/function/method/type-parameter positions. - Rewrite invalid JSDoc
@paramnames into sanitized identifiers (replacing non-id chars with_, falling back to_<index>), and JSDoc@propertynames into string literals so emit produces quoted property names. - Add a new compiler test
jsdocNonIdentifierPropertiesAndParamsand update affected submodule baselines (mostly adding the newTS1003 Identifier expecteddiagnostic and one converged: error→: anytypes output).
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/parser/reparser.go | Core change: new checkNonIdentifierName/addTransformedReparse helpers and rewriting of param/property names; threads checks through typedef, callback, signature, and type-parameter reparse paths. |
| testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts | New compiler test exercising hyphenated @property names and a hyphenated @param name in a @callback. |
| testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.{js,types,symbols} | New baselines verifying quoted property names in .d.ts and props_like sanitized parameter name. |
| testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.* | New diagnostic for nameless typedef trailing junk; type for Request access now resolves to any (closer to TS). |
| testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash{,2}.errors.txt(.diff) | New Identifier expected diagnostic on nameless @typedef {{ }}. |
| testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt(.diff) | Adds extra Identifier expected diagnostic from nameless @typedef {string}. |
| testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt(.diff) | Same: extra Identifier expected on nameless typedef. |
| testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt(.diff) | Same diagnostic added at the unparseable typedef site. |
| testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt(.diff) | New Identifier expected at the C~B typedef name. |
| testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt(.diff) | New Identifier expected for nameless function-typed typedefs across mod1/mod3/mod4. |
| name := jsparam.Name() | ||
| if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { | ||
| // drop invalid chars for _, if empty, write _0, etc., so we have a valid param name to emit later | ||
| result := strings.Builder{} | ||
| for i, ch := range name.AsIdentifier().Text { | ||
| if i == 0 { | ||
| if !scanner.IsIdentifierStart(ch) { | ||
| result.WriteRune('_') | ||
| } else { | ||
| result.WriteRune(ch) | ||
| } | ||
| continue | ||
| } else if !scanner.IsIdentifierPart(ch) { | ||
| result.WriteRune('_') | ||
| } else { | ||
| result.WriteRune(ch) | ||
| } | ||
| } | ||
| if result.Len() == 0 { | ||
| result.WriteRune('_') | ||
| result.WriteString(strconv.Itoa(pi)) | ||
| } | ||
| name = p.addTransformedReparse(p.factory.NewIdentifier(result.String()), name) | ||
| } else { | ||
| name = p.addDeepCloneReparse(name) | ||
| } |
|
Can we give a more-specialized error message for reserved names? |
|
@DanielRosenwasser nope, because they're not an error. We're not checking against keywords (which are valid identifiers in many locations), just the actual characters allowed in js identifiers (eg, forbidding a |
Fixes #4011
A lot of the changed baselines are a new
Identifier expectederror on the now unsupported namelesstypedefpattern (which used to pull a name from the next expression, but currently parses an empty string for the name).