-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Deduplicate protocol.ts content #57361
Deduplicate protocol.ts content #57361
Conversation
Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @mjbvz, @zkat, and @joj for you. Feel free to loop in other consumers/maintainers if necessary. |
/** | ||
* A set of one or more available refactoring actions, grouped under a parent refactoring. | ||
*/ | ||
export interface ApplicableRefactorInfo { |
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.
This is an example of an interface that was copied exactly from services/types.ts. This can simply be imported instead of duplicated.
*/ | ||
kind: OutliningSpanKind; | ||
} | ||
export type OutliningSpan = ChangePropertyTypes<ts.OutliningSpan, { textSpan: TextSpan; hintSpan: TextSpan; }>; |
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.
This is an example of an interface that was copied from services/types.ts with a couple changed property types. I added a utility for this purpose. I only used it in cases where the majority of properties were identical; when there were many changes, I let the near-duplicate stand. Note that this is a homomorphic mapped type so JSDoc from the source type’s properties is retained.
import { | ||
ClassificationType, | ||
CompletionTriggerKind, | ||
OrganizeImportsMode, | ||
SemicolonPreference, | ||
} from "./_namespaces/ts"; |
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.
These were identical copies of const enums previously duplicated in this file, which can simply be imported now. Since we have preserveConstEnums
on, they have to be imported without import type
and re-exported below to ensure the shape of this module doesn’t change. (I validated this manually by comparing Object.keys(ts.server.protocol)
before and after this PR.)
ReactNative = "ReactNative", | ||
React = "React", | ||
None = "none", | ||
Preserve = "preserve", | ||
ReactNative = "react-native", | ||
React = "react", | ||
ReactJSX = "react-jsx", | ||
ReactJSXDev = "react-jsxdev", |
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.
All of these compiler options enums were missing values, but this one also had a value ReactNative = "ReactNative"
which has forever been broken, as far as I can tell. When inferred or external compiler options are sent over the wire, enum-typed properties are allowed to be, for example, ts.JsxEmit | protocol.JsxEmit
; i.e. a number or a corresponding string value. However, the conversion method simply uses the same parser as command-line or tsconfig values, which means these string values need to match the ones in the maps defined in commandLineParser.ts
, which are kebab-cased. Uppercase/lowercase letters don’t matter, but "ReactNative"
would not have worked since it was missing the hyphen.
I changed all of these enum values to lowercase for consistency, but react-native
was the only one that strictly matters. This is technically a type-level breaking change.
type AssertKeysComplete<Source extends { [K in keyof Target]: any; }, Target> = Source; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
type CopiedTypesComplete = [ | ||
AssertKeysComplete<typeof ModuleResolutionKind, typeof ts.ModuleResolutionKind>, | ||
AssertKeysComplete<typeof ModuleKind, typeof ts.ModuleKind>, | ||
AssertKeysComplete<typeof ScriptTarget, typeof ts.ScriptTarget>, | ||
AssertKeysComplete<typeof JsxEmit, typeof ts.JsxEmit>, | ||
AssertKeysComplete<typeof IndentStyle, typeof ts.IndentStyle>, | ||
]; |
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.
As mentioned before, what really matters is that the stringified enums represent every value for each option that the command line parser can recognize. Those strings aren’t encoded in types anywhere else that I know of, so rather than checking that directly, these checks just make sure each enum has all the keys the source enum has, protecting us from forgetting to update the protocol when compiler options get new values. The enum values here in the protocol could still be typo’d, as was the case for "ReactNative"
Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page. Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up. |
// We didn't find the symbol in scope at all. Just allow it and we'll fail at test time. | ||
return node; |
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.
Maybe not for this PR, but I do feel a bit like I should have originally just made this error.
@@ -310,6 +318,10 @@ function verifyMatchingSymbols(decl) { | |||
} | |||
const symbolInScope = findInScope(symbolOfNode.name); | |||
if (!symbolInScope) { | |||
if (symbolOfNode.declarations?.every(d => isLocalDeclaration(d) && d.getSourceFile() === decl.getSourceFile()) && !isSelfReference(node, symbolOfNode)) { | |||
// The symbol is a local that needs to be copied into the scope. | |||
scopeStack[scopeStack.length - 1].locals.set(symbolOfNode.name, { symbol: symbolOfNode, writeTarget: isInternal ? WriteTarget.Internal : WriteTarget.Both }); |
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 seems moderately weird for this verification function to modify the locals, but not sure what's better.
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 feel like I considered either renaming the function or returning the set of new locals to be appended before I paused this 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.
@jakebailey any more thoughts on this? I agree this code isn’t great. Otherwise I think the current state is ready for review.
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.
No, not really; I think it's fine for now. Thankfully I don't think the bundler has that high of a cognitive load nor changes that much 😄
fe72547
to
e531c0b
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.
LGTM, though at some level I wonder if we should switch everything to export
now such that future PRs which modify the top level API don't have to incur the diff change. But, another PR for that would be fine.
(I'm waffling on whether or not an export modifier is good or bad at this point; before we didn't have any so I was happy because it meant a smaller package.)
Fixes #57232 and improves some quality of life, hopefully