-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Add new special assignment kinds for recognizing Object.defineProperty calls #27208
Add new special assignment kinds for recognizing Object.defineProperty calls #27208
Conversation
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.
Initial comments. Most of them are nitpicky, and I haven't looked at binder or checker yet.
src/compiler/utilities.ts
Outdated
const special = getAssignmentDeclarationKindWorker(expr); | ||
return special === AssignmentDeclarationKind.Property || isInJSFile(expr) ? special : AssignmentDeclarationKind.None; | ||
} | ||
|
||
function getAssignmentDeclarationKindWorker(expr: BinaryExpression): AssignmentDeclarationKind { | ||
export function isBindableObjectDefinePropertyCall(expr: CallExpression): expr is BindableObjectDefinePropertyCall { | ||
return !(length(expr.arguments) !== 3 || |
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.
! ... !== .. ||
? Why not write it as ... === ... &&
?
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.
Also, I thought you said the third one was optional? I guess we won't be able to get the type in that case, but we could still bind the property with type any.
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 not optional, it just has a few different shapes it can take.
src/compiler/checker.ts
Outdated
function isReadonlySymbol(symbol: Symbol): boolean { | ||
// The following symbols are considered read-only: | ||
// Properties with a 'readonly' modifier | ||
// Variables declared with 'const' | ||
// Get accessors without matching set accessors | ||
// Enum members | ||
// JS Assignments with writable false or no setter |
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.
pedantically (since I spent a day thinking about terms), it's better to say // define-property assignments with writable false or no setter
. This parallels terminology of things that are parllel, like "this-property assignments" or "prototype assignments".
src/compiler/types.ts
Outdated
/** @internal */ | ||
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; | ||
|
||
|
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.
extra newline
src/compiler/utilities.ts
Outdated
case SyntaxKind.PropertyAssignment: | ||
return init.initializer; | ||
case SyntaxKind.ShorthandPropertyAssignment: | ||
case SyntaxKind.MethodDeclaration: |
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.
which ones are allowed to be methods? get? set? value?
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.
Get and set often are. Value can be, it'd be the same as a property with a function expression value, though.
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 this function is unused in my current implementation, though.
src/compiler/utilities.ts
Outdated
return AssignmentDeclarationKind.None; | ||
} | ||
const entityName = expr.arguments[0]; | ||
if ((isIdentifier(entityName) && entityName.escapedText === "exports") || (isPropertyAccessExpression(entityName) && isIdentifier(entityName.expression) && entityName.expression.escapedText === "module" && entityName.name.escapedText === "exports")) { |
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.
newline needed in here somewhere
#6651 definitely is included here. At least for JS. |
I think I need extra handling to cover the prototype assignment case as in |
@sandersn I've added support for prototype property assignments. |
case AssignmentDeclarationKind.None: | ||
break; // Nothing to do | ||
default: | ||
return Debug.fail("Unknown call expression assignment declaration kind"); |
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.
Debug.assertNever is better here I think.
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.
assignmentKind
has all the non-object-define values still "possible" (they're not, because it's a call expression and not a binary expression) and so isn't never; so not quite?
src/compiler/utilities.ts
Outdated
return AssignmentDeclarationKind.None; | ||
} | ||
const entityName = expr.arguments[0]; | ||
if ((isIdentifier(entityName) && entityName.escapedText === "exports") || |
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 we should have a is[Module]Exports utility since this is repeated for the ExportsProperty case below.
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.
Hilariously enough, 10 lines up we already have one, with exactly what i was going to name it.
src/compiler/checker.ts
Outdated
function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { | ||
if (isCallExpression(expression)) { | ||
if (resolvedSymbol) { | ||
return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments |
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 there a test for this case?
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.
AFAIK I can't construct something that can actually hit this codepath currently (since something like Object.defineProperty(module, "exports", { value: "no" })
won't be bound to the module in the first place); but I wanna avoid crashing and burning if we change something, and this seems reasonable (ignoring the reassignment). I'll add a test with similar content that could trigger it if we ever do start binding it a bit differently, though.
src/compiler/checker.ts
Outdated
isObjectLiteralExpression(container.parent.parent) && | ||
isCallExpression(container.parent.parent.parent) && | ||
container.parent.parent.parent.arguments[2] === container.parent.parent && | ||
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { |
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 applies to all the cases in this function) Would it be possible, and more efficient, to just check that parent x 3 exists and then just check the result of getAssignmentDeclarationKind? I think the rest of the predicate is duplicated with it.
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.
Not quite - these are actually some extra constraints which ask that the third argument be specifically an object literal (rather than anything we might be able to pull a type from), to inject a this
type into the method defined in it, even if Object.defineProperty
's signature lacks a strong this
(and, in fact, it does lack one).
src/compiler/checker.ts
Outdated
case AssignmentDeclarationKind.ObjectDefinePropertyValue: | ||
case AssignmentDeclarationKind.ObjectDefinePropertyExports: | ||
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: | ||
return Debug.fail("Unimplemented"); |
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.
"Not yet implemented" or "Does not apply"? Isn't it the latter?
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.
Yea, definitely the later.
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.
Some questions and suggestions, plus I need to look at the tests.
Can you see what happens with the user tests? (and maybe RWC, although I don't think there's much JS there.) |
|
||
==== tests/cases/conformance/jsdoc/index.js (0 errors) ==== | ||
const x = {}; | ||
Object.defineProperty(x, "name", { value: "Charles", writable: true }); |
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.
you should have a value test that leaves off writable, if that's allowed. Actually, you should have a test with value left out too. And maybe a test with some of the properties we don't care about?
Also a test with non-literals, just in case that hurts our ... error reporting or something?
According to MDN's Object.defineProperty page, we should have an error if both get/set and value are present. I didn't see a test for that either.
Also, who spells 'writeable' as 'writable'?
Later:everybody else, it turns out.
@sandersn I've replied to or addressed all of your comments; how'd you feel about this now? |
Looks good except for four things:
|
The function is still typechecked as a function call, so that aughta still be happening provided the |
OK, then I guess we should consider changing it afterward. It would probably break some code, though. |
@weswigham It would be nice to get this one in. Will you have time to add those tests some time? |
Yeah, I'll take some time to add them today. |
@sandersn done |
Sorry to comment on a long closed issue, but is this also implemented for |
Nope. |
Does this still work? It's not for me in VScode: Version: 1.59.0 Suppose the following code:test.ts: const something = {
somethingValue: 'test'
}
Object.defineProperty(something, 'definedProperty', {
value: 'hello'
})
export default something test2.ts: import Thing from './test'
Thing.somethingValue // this is okay
Thing.definedProperty // this will have red curly underline So why does |
We only recognize them in .js files, not .ts. |
I think it's time for a .ts implementation too! |
As special property assignments in JS.
Fixes #25854
Fixes #26082
Fixes #6651