diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9922f9b4e9288..1329d6d2a29de 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2797,9 +2797,38 @@ namespace ts { (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); - const isMethod = isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!); - const includes = isMethod ? SymbolFlags.Method : SymbolFlags.Property; - const excludes = isMethod ? SymbolFlags.MethodExcludes : SymbolFlags.PropertyExcludes; + let includes = SymbolFlags.None; + let excludes = SymbolFlags.None; + // Method-like + if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { + includes = SymbolFlags.Method; + excludes = SymbolFlags.MethodExcludes; + } + // Maybe accessor-like + else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "set"; + })) { + // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this + // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) + includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.SetAccessorExcludes; + } + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "get"; + })) { + includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.GetAccessorExcludes; + } + } + + if (includes === SymbolFlags.None) { + includes = SymbolFlags.Property; + excludes = SymbolFlags.PropertyExcludes; + } + declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0731ccbf04c76..2f92d7f35dbb7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4916,8 +4916,8 @@ namespace ts { } function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { - const serializePropertySymbolForClass = makeSerializePropertySymbol(createProperty, SyntaxKind.MethodDeclaration); - const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature); + const serializePropertySymbolForClass = makeSerializePropertySymbol(createProperty, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type, initializer) => createPropertySignature(mods, name, question, type, initializer), SyntaxKind.MethodSignature, /*useAcessors*/ false); // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of // declaration mapping @@ -5734,7 +5734,23 @@ namespace ts { questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined - ) => T, methodKind: SyntaxKind): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]) { + ) => T, methodKind: SyntaxKind, useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); + function makeSerializePropertySymbol(createProperty: ( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SyntaxKind, useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); + function makeSerializePropertySymbol(createProperty: ( + decorators: readonly Decorator[] | undefined, + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SyntaxKind, useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined) { if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols @@ -5750,7 +5766,40 @@ namespace ts { const staticFlag = isStatic ? ModifierFlags.Static : 0; const rawName = unescapeLeadingUnderscores(p.escapedName); const name = getPropertyNameNodeForSymbolFromNameType(p, context) || createIdentifier(rawName); - if (p.flags & (SymbolFlags.Property | SymbolFlags.Accessor | SymbolFlags.Variable)) { + const firstPropertyLikeDecl = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); + if (p.flags & SymbolFlags.Accessor && useAccessors) { + const result: AccessorDeclaration[] = []; + if (p.flags & SymbolFlags.SetAccessor) { + result.push(setTextRange(createSetAccessor( + /*decorators*/ undefined, + createModifiersFromModifierFlags(staticFlag), + name, + [createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "arg", + /*questionToken*/ undefined, + serializeTypeForDeclaration(getTypeOfSymbol(p), p) + )], + /*body*/ undefined + ), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl)); + } + if (p.flags & SymbolFlags.GetAccessor) { + result.push(setTextRange(createGetAccessor( + /*decorators*/ undefined, + createModifiersFromModifierFlags(staticFlag), + name, + [], + serializeTypeForDeclaration(getTypeOfSymbol(p), p), + /*body*/ undefined + ), find(p.declarations, isGetAccessor) || firstPropertyLikeDecl)); + } + return result; + } + // This is an else/if as accessors and properties can't merge in TS, but might in JS + // If this happens, we assume the accessor takes priority, as it imposes more constraints + else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable)) { return setTextRange(createProperty( /*decorators*/ undefined, createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | staticFlag), @@ -5760,7 +5809,7 @@ namespace ts { // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 // interface members can't have initializers, however class members _can_ /*initializer*/ undefined - ), filter(p.declarations, d => isPropertyDeclaration(d) || isAccessor(d) || isVariableDeclaration(d) || isPropertySignature(d) || isBinaryExpression(d) || isPropertyAccessExpression(d))[0]); + ), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); } if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { const type = getTypeOfSymbol(p); @@ -7340,7 +7389,7 @@ namespace ts { } } else { - Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function"); + Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); errorOrSuggestion(noImplicitAny, getter!, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } return anyType; diff --git a/tests/baselines/reference/jsDeclarationsClasses.js b/tests/baselines/reference/jsDeclarationsClasses.js index d94e8690a3c58..88e5991b09f95 100644 --- a/tests/baselines/reference/jsDeclarationsClasses.js +++ b/tests/baselines/reference/jsDeclarationsClasses.js @@ -479,18 +479,22 @@ export class E { */ static staticReadonlyField: string; static staticInitializedField: number; + /** + * @param {string} _p + */ + static set s1(arg: string); /** * @return {string} */ - static s1: string; + static get s1(): string; /** * @return {string} */ - static readonly s2: string; + static get s2(): string; /** * @param {string} _p */ - static s3: string; + static set s3(arg: string); /** * @param {T} a * @param {U} b @@ -506,18 +510,22 @@ export class E { */ readonlyField: T & U; initializedField: number; + /** + * @param {U} _p + */ + set f1(arg: U); /** * @return {U} */ - f1: U; + get f1(): U; /** * @return {U} */ - readonly f2: U; + get f2(): U; /** * @param {U} _p */ - f3: U; + set f3(arg: U); } /** * @template T,U diff --git a/tests/baselines/reference/jsDeclarationsFunctionLikeClasses2.js b/tests/baselines/reference/jsDeclarationsFunctionLikeClasses2.js index 86aecfef4c961..cab9442774ba4 100644 --- a/tests/baselines/reference/jsDeclarationsFunctionLikeClasses2.js +++ b/tests/baselines/reference/jsDeclarationsFunctionLikeClasses2.js @@ -184,8 +184,16 @@ export class Point2D { * @param {number} y */ constructor(x: number, y: number); - x: number; - y: number; + /** + * @param {number} x + */ + set x(arg: number); + get x(): number; + /** + * @param {number} y + */ + set y(arg: number); + get y(): number; __proto__: typeof Vec; } //// [referencer.d.ts] diff --git a/tests/baselines/reference/jsDeclarationsGetterSetter.js b/tests/baselines/reference/jsDeclarationsGetterSetter.js new file mode 100644 index 0000000000000..5aa0d93cb6dc0 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsGetterSetter.js @@ -0,0 +1,122 @@ +//// [index.js] +export class A { + get x() { + return 12; + } +} + +export class B { + /** + * @param {number} _arg + */ + set x(_arg) { + } +} + +export class C { + get x() { + return 12; + } + set x(_arg) { + } +} + +export class D {} +Object.defineProperty(D.prototype, "x", { + get() { + return 12; + } +}); + +export class E {} +Object.defineProperty(E.prototype, "x", { + /** + * @param {number} _arg + */ + set(_arg) {} +}); + +export class F {} +Object.defineProperty(F.prototype, "x", { + get() { + return 12; + }, + /** + * @param {number} _arg + */ + set(_arg) {} +}); + + +//// [index.js] +export class A { + get x() { + return 12; + } +} +export class B { + /** + * @param {number} _arg + */ + set x(_arg) { + } +} +export class C { + get x() { + return 12; + } + set x(_arg) { + } +} +export class D { +} +Object.defineProperty(D.prototype, "x", { + get() { + return 12; + } +}); +export class E { +} +Object.defineProperty(E.prototype, "x", { + /** + * @param {number} _arg + */ + set(_arg) { } +}); +export class F { +} +Object.defineProperty(F.prototype, "x", { + get() { + return 12; + }, + /** + * @param {number} _arg + */ + set(_arg) { } +}); + + +//// [index.d.ts] +export class A { + get x(): number; +} +export class B { + /** + * @param {number} _arg + */ + set x(arg: number); +} +export class C { + set x(arg: number); + get x(): number; +} +export class D { + get x(): number; +} +export class E { + set x(arg: number); +} +export class F { + set x(arg: number); + get x(): number; +} diff --git a/tests/baselines/reference/jsDeclarationsGetterSetter.symbols b/tests/baselines/reference/jsDeclarationsGetterSetter.symbols new file mode 100644 index 0000000000000..d7eb6d054d104 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsGetterSetter.symbols @@ -0,0 +1,103 @@ +=== tests/cases/conformance/jsdoc/declarations/index.js === +export class A { +>A : Symbol(A, Decl(index.js, 0, 0)) + + get x() { +>x : Symbol(A.x, Decl(index.js, 0, 16)) + + return 12; + } +} + +export class B { +>B : Symbol(B, Decl(index.js, 4, 1)) + + /** + * @param {number} _arg + */ + set x(_arg) { +>x : Symbol(B.x, Decl(index.js, 6, 16)) +>_arg : Symbol(_arg, Decl(index.js, 10, 10)) + } +} + +export class C { +>C : Symbol(C, Decl(index.js, 12, 1)) + + get x() { +>x : Symbol(C.x, Decl(index.js, 14, 16), Decl(index.js, 17, 5)) + + return 12; + } + set x(_arg) { +>x : Symbol(C.x, Decl(index.js, 14, 16), Decl(index.js, 17, 5)) +>_arg : Symbol(_arg, Decl(index.js, 18, 10)) + } +} + +export class D {} +>D : Symbol(D, Decl(index.js, 20, 1)) + +Object.defineProperty(D.prototype, "x", { +>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>D.prototype : Symbol(D.prototype) +>D : Symbol(D, Decl(index.js, 20, 1)) +>prototype : Symbol(D.prototype) +>"x" : Symbol(D.x, Decl(index.js, 22, 17)) + + get() { +>get : Symbol(get, Decl(index.js, 23, 41)) + + return 12; + } +}); + +export class E {} +>E : Symbol(E, Decl(index.js, 27, 3)) + +Object.defineProperty(E.prototype, "x", { +>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>E.prototype : Symbol(E.prototype) +>E : Symbol(E, Decl(index.js, 27, 3)) +>prototype : Symbol(E.prototype) +>"x" : Symbol(E.x, Decl(index.js, 29, 17)) + + /** + * @param {number} _arg + */ + set(_arg) {} +>set : Symbol(set, Decl(index.js, 30, 41)) +>_arg : Symbol(_arg, Decl(index.js, 34, 8)) + +}); + +export class F {} +>F : Symbol(F, Decl(index.js, 35, 3)) + +Object.defineProperty(F.prototype, "x", { +>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>F.prototype : Symbol(F.prototype) +>F : Symbol(F, Decl(index.js, 35, 3)) +>prototype : Symbol(F.prototype) +>"x" : Symbol(F.x, Decl(index.js, 37, 17)) + + get() { +>get : Symbol(get, Decl(index.js, 38, 41)) + + return 12; + }, + /** + * @param {number} _arg + */ + set(_arg) {} +>set : Symbol(set, Decl(index.js, 41, 6)) +>_arg : Symbol(_arg, Decl(index.js, 45, 8)) + +}); + diff --git a/tests/baselines/reference/jsDeclarationsGetterSetter.types b/tests/baselines/reference/jsDeclarationsGetterSetter.types new file mode 100644 index 0000000000000..c426e822b125c --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsGetterSetter.types @@ -0,0 +1,114 @@ +=== tests/cases/conformance/jsdoc/declarations/index.js === +export class A { +>A : A + + get x() { +>x : number + + return 12; +>12 : 12 + } +} + +export class B { +>B : B + + /** + * @param {number} _arg + */ + set x(_arg) { +>x : number +>_arg : number + } +} + +export class C { +>C : C + + get x() { +>x : number + + return 12; +>12 : 12 + } + set x(_arg) { +>x : number +>_arg : number + } +} + +export class D {} +>D : D + +Object.defineProperty(D.prototype, "x", { +>Object.defineProperty(D.prototype, "x", { get() { return 12; }}) : any +>Object.defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>Object : ObjectConstructor +>defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>D.prototype : D +>D : typeof D +>prototype : D +>"x" : "x" +>{ get() { return 12; }} : { get(): number; } + + get() { +>get : () => number + + return 12; +>12 : 12 + } +}); + +export class E {} +>E : E + +Object.defineProperty(E.prototype, "x", { +>Object.defineProperty(E.prototype, "x", { /** * @param {number} _arg */ set(_arg) {}}) : any +>Object.defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>Object : ObjectConstructor +>defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>E.prototype : E +>E : typeof E +>prototype : E +>"x" : "x" +>{ /** * @param {number} _arg */ set(_arg) {}} : { set(_arg: number): void; } + + /** + * @param {number} _arg + */ + set(_arg) {} +>set : (_arg: number) => void +>_arg : number + +}); + +export class F {} +>F : F + +Object.defineProperty(F.prototype, "x", { +>Object.defineProperty(F.prototype, "x", { get() { return 12; }, /** * @param {number} _arg */ set(_arg) {}}) : any +>Object.defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>Object : ObjectConstructor +>defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>F.prototype : F +>F : typeof F +>prototype : F +>"x" : "x" +>{ get() { return 12; }, /** * @param {number} _arg */ set(_arg) {}} : { get(): number; set(_arg: number): void; } + + get() { +>get : () => number + + return 12; +>12 : 12 + + }, + /** + * @param {number} _arg + */ + set(_arg) {} +>set : (_arg: number) => void +>_arg : number + +}); + diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsGetterSetter.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsGetterSetter.ts new file mode 100644 index 0000000000000..05d6769f050ab --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsGetterSetter.ts @@ -0,0 +1,53 @@ +// @allowJs: true +// @checkJs: true +// @outDir: ./out +// @target: es6 +// @declaration: true +// @filename: index.js +export class A { + get x() { + return 12; + } +} + +export class B { + /** + * @param {number} _arg + */ + set x(_arg) { + } +} + +export class C { + get x() { + return 12; + } + set x(_arg) { + } +} + +export class D {} +Object.defineProperty(D.prototype, "x", { + get() { + return 12; + } +}); + +export class E {} +Object.defineProperty(E.prototype, "x", { + /** + * @param {number} _arg + */ + set(_arg) {} +}); + +export class F {} +Object.defineProperty(F.prototype, "x", { + get() { + return 12; + }, + /** + * @param {number} _arg + */ + set(_arg) {} +});