From 1af2a7f5bc090f82f45aad959029f11d7c3a2710 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:11:38 -0800 Subject: [PATCH 1/8] Allow mixins to describe custom elements Adds mixins docs too --- schema.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/schema.ts b/schema.ts index 3496512..19f5250 100644 --- a/schema.ts +++ b/schema.ts @@ -131,7 +131,7 @@ export type Declaration = | FunctionDeclaration | MixinDeclaration | VariableDeclaration - | CustomElement; + | CustomElementDeclaration; /** * A reference to an export of a module. @@ -149,7 +149,7 @@ export interface Reference { } /** - * Description of a custom element class. + * A description of a custom element class. * * Custom elements are JavaScript classes, so this extends from * `ClassDeclaration` and adds custom-element-specific features like @@ -168,7 +168,13 @@ export interface Reference { * tagName, and another `Module` should contain the * `CustomElement`. */ -export interface CustomElement extends ClassDeclaration { +export interface CustomElementDeclaration extends ClassDeclaration {} + + +/** + * The additional fields that a custom element adds to classes and mixins. + */ +export interface CustomElement extends ClassLike { /** * An optional tag name that should be specified if this is a * self-registering element. @@ -193,7 +199,7 @@ export interface CustomElement extends ClassDeclaration { */ slots?: Slot[]; - parts?: CssPart[]; + cssParts?: CssPart[]; cssProperties?: CssCustomProperty[]; @@ -404,9 +410,52 @@ export interface ClassMethod extends FunctionLike { } /** - * + * A description of a mixin. + * + * Mixins are functions which generate a new subclass of a given superclass. + * This interfaces describes the class and custom element features that + * are added by the mixin. As such, it extends the CustomElement interface and + * ClassLike interface. + * + * Since mixins are functions, it also extends the FunctionLike interface. This + * means a mixin is callable, and has parameters and a return type. + * + * The return type is often hard or impossible to accurately describe in type + * systems like TypeScript. It requires generics and an `exrtends` operator + * that TypeScript lacks. Therefore it's reccomended that the return type is + * left empty. The most common form of a mixin function takes a single + * argument, so consumers of this interface should assume that the return type + * is the single argument subclassed by this declaration. + * + * @example + * + * This JavaScript mixin declaration: + * ```javascript + * const MyMixin = (base) => class extends base { + * foo() { ... } + * } + * ``` + * + * Is described by this JSON: + * ```json + * { + * "kind": "mixin", + * "name": "MyMixin", + * "parameters": [ + * { + * "name": "base", + * } + * ], + * "members": [ + * { + * "kind": "method", + * "name": "foo", + * } + * ] + * } + * ``` */ -export interface MixinDeclaration extends ClassLike, FunctionLike { +export interface MixinDeclaration extends CustomElement, FunctionLike { kind: 'mixin'; } From ab1ec94ab243b04980a726167ee58e5ebc6f0c00 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:31:46 -0800 Subject: [PATCH 2/8] Update schema.ts Co-authored-by: Pascal Schilp --- schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.ts b/schema.ts index 19f5250..33a82e2 100644 --- a/schema.ts +++ b/schema.ts @@ -421,7 +421,7 @@ export interface ClassMethod extends FunctionLike { * means a mixin is callable, and has parameters and a return type. * * The return type is often hard or impossible to accurately describe in type - * systems like TypeScript. It requires generics and an `exrtends` operator + * systems like TypeScript. It requires generics and an `extends` operator * that TypeScript lacks. Therefore it's reccomended that the return type is * left empty. The most common form of a mixin function takes a single * argument, so consumers of this interface should assume that the return type From 2d1bb35a95ef3017f21362183b4f28fce9e9f17b Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:31:55 -0800 Subject: [PATCH 3/8] Update schema.ts Co-authored-by: Pascal Schilp --- schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.ts b/schema.ts index 33a82e2..e8e8c33 100644 --- a/schema.ts +++ b/schema.ts @@ -422,7 +422,7 @@ export interface ClassMethod extends FunctionLike { * * The return type is often hard or impossible to accurately describe in type * systems like TypeScript. It requires generics and an `extends` operator - * that TypeScript lacks. Therefore it's reccomended that the return type is + * that TypeScript lacks. Therefore it's recommended that the return type is * left empty. The most common form of a mixin function takes a single * argument, so consumers of this interface should assume that the return type * is the single argument subclassed by this declaration. From d1e77b0626b38a7d87b327ea26fce4aa879a1e59 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:42:07 -0800 Subject: [PATCH 4/8] Add docs about mixin superclasses --- schema.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schema.ts b/schema.ts index e8e8c33..4065dc6 100644 --- a/schema.ts +++ b/schema.ts @@ -427,6 +427,9 @@ export interface ClassMethod extends FunctionLike { * argument, so consumers of this interface should assume that the return type * is the single argument subclassed by this declaration. * + * A mixin should only have a superclass if it composes another mixin, and the + * superclass should reference that mixin. + * * @example * * This JavaScript mixin declaration: From f5ca04760862d5ffa3d119e088f583d72be7012d Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:42:25 -0800 Subject: [PATCH 5/8] format --- schema.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/schema.ts b/schema.ts index 4065dc6..341a0a6 100644 --- a/schema.ts +++ b/schema.ts @@ -170,7 +170,6 @@ export interface Reference { */ export interface CustomElementDeclaration extends ClassDeclaration {} - /** * The additional fields that a custom element adds to classes and mixins. */ @@ -411,34 +410,34 @@ export interface ClassMethod extends FunctionLike { /** * A description of a mixin. - * + * * Mixins are functions which generate a new subclass of a given superclass. * This interfaces describes the class and custom element features that * are added by the mixin. As such, it extends the CustomElement interface and * ClassLike interface. - * + * * Since mixins are functions, it also extends the FunctionLike interface. This * means a mixin is callable, and has parameters and a return type. - * + * * The return type is often hard or impossible to accurately describe in type * systems like TypeScript. It requires generics and an `extends` operator * that TypeScript lacks. Therefore it's recommended that the return type is * left empty. The most common form of a mixin function takes a single * argument, so consumers of this interface should assume that the return type * is the single argument subclassed by this declaration. - * + * * A mixin should only have a superclass if it composes another mixin, and the * superclass should reference that mixin. - * + * * @example - * + * * This JavaScript mixin declaration: * ```javascript * const MyMixin = (base) => class extends base { * foo() { ... } * } * ``` - * + * * Is described by this JSON: * ```json * { From 5ffe59fa9b07c3dd4e934c8b86119e79c761f055 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 11:42:33 -0800 Subject: [PATCH 6/8] regenerate schema json --- schema.json | 171 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 48 deletions(-) diff --git a/schema.json b/schema.json index 9869918..781ef9f 100644 --- a/schema.json +++ b/schema.json @@ -15,6 +15,9 @@ "description": "The name of the field this attribute is associated with, if any.", "type": "string" }, + "inheritedFrom": { + "$ref": "#/definitions/Reference" + }, "name": { "type": "string" }, @@ -216,39 +219,13 @@ ], "type": "object" }, - "CustomElement": { - "description": "Description of a custom element class.\n\nCustom elements are JavaScript classes, so this extends from\n`ClassDeclaration` and adds custom-element-specific features like\nattributes, events, and slots.\n\nNote that `tagName` in this interface is optional. Tag names are not\nneccessarily part of a custom element class, but belong to the definition\n(often called the \"registration\") or the `customElements.define()` call.\n\nBecause classes and tag anmes can only be registered once, there's a\none-to-one relationship between classes and tag names. For ease of use,\nwe allow the tag name here.\n\nSome packages define and register custom elements in separate modules. In\nthese cases one `Module` should contain the `CustomElement` without a\ntagName, and another `Module` should contain the\n`CustomElement`.", + "CustomElementDeclaration": { + "description": "A description of a custom element class.\n\nCustom elements are JavaScript classes, so this extends from\n`ClassDeclaration` and adds custom-element-specific features like\nattributes, events, and slots.\n\nNote that `tagName` in this interface is optional. Tag names are not\nneccessarily part of a custom element class, but belong to the definition\n(often called the \"registration\") or the `customElements.define()` call.\n\nBecause classes and tag anmes can only be registered once, there's a\none-to-one relationship between classes and tag names. For ease of use,\nwe allow the tag name here.\n\nSome packages define and register custom elements in separate modules. In\nthese cases one `Module` should contain the `CustomElement` without a\ntagName, and another `Module` should contain the\n`CustomElement`.", "properties": { - "attributes": { - "description": "The attributes that this element is known to understand.", - "items": { - "$ref": "#/definitions/Attribute" - }, - "type": "array" - }, - "cssProperties": { - "items": { - "$ref": "#/definitions/CssCustomProperty" - }, - "type": "array" - }, - "demos": { - "items": { - "$ref": "#/definitions/Demo" - }, - "type": "array" - }, "description": { "description": "A markdown description of the class.", "type": "string" }, - "events": { - "description": "The events that this element fires.", - "items": { - "$ref": "#/definitions/Event" - }, - "type": "array" - }, "kind": { "enum": [ "class" @@ -277,29 +254,12 @@ "name": { "type": "string" }, - "parts": { - "items": { - "$ref": "#/definitions/CssPart" - }, - "type": "array" - }, - "slots": { - "description": "The shadow dom content slots that this element accepts.", - "items": { - "$ref": "#/definitions/Slot" - }, - "type": "array" - }, "summary": { "description": "A markdown summary suitable for display in a listing.", "type": "string" }, "superclass": { "$ref": "#/definitions/Reference" - }, - "tagName": { - "description": "An optional tag name that should be specified if this is a\nself-registering element.\n\nSelf-registering elements must also include a CustomElementExport\nin the module's exports.", - "type": "string" } }, "required": [ @@ -355,6 +315,9 @@ "description": "A markdown description.", "type": "string" }, + "inheritedFrom": { + "$ref": "#/definitions/Reference" + }, "name": { "type": "string" }, @@ -452,11 +415,14 @@ { "$ref": "#/definitions/FunctionDeclaration" }, + { + "$ref": "#/definitions/MixinDeclaration" + }, { "$ref": "#/definitions/VariableDeclaration" }, { - "$ref": "#/definitions/CustomElement" + "$ref": "#/definitions/CustomElementDeclaration" } ] }, @@ -501,6 +467,115 @@ ], "type": "object" }, + "MixinDeclaration": { + "description": "A description of a mixin.\n\nMixins are functions which generate a new subclass of a given superclass.\nThis interfaces describes the class and custom element features that\nare added by the mixin. As such, it extends the CustomElement interface and\nClassLike interface.\n\nSince mixins are functions, it also extends the FunctionLike interface. This\nmeans a mixin is callable, and has parameters and a return type.\n\nThe return type is often hard or impossible to accurately describe in type\nsystems like TypeScript. It requires generics and an `extends` operator\nthat TypeScript lacks. Therefore it's recommended that the return type is\nleft empty. The most common form of a mixin function takes a single\nargument, so consumers of this interface should assume that the return type\nis the single argument subclassed by this declaration.\n\nA mixin should only have a superclass if it composes another mixin, and the\nsuperclass should reference that mixin.", + "properties": { + "attributes": { + "description": "The attributes that this element is known to understand.", + "items": { + "$ref": "#/definitions/Attribute" + }, + "type": "array" + }, + "cssParts": { + "items": { + "$ref": "#/definitions/CssPart" + }, + "type": "array" + }, + "cssProperties": { + "items": { + "$ref": "#/definitions/CssCustomProperty" + }, + "type": "array" + }, + "demos": { + "items": { + "$ref": "#/definitions/Demo" + }, + "type": "array" + }, + "description": { + "description": "A markdown description of the class.", + "type": "string" + }, + "events": { + "description": "The events that this element fires.", + "items": { + "$ref": "#/definitions/Event" + }, + "type": "array" + }, + "kind": { + "enum": [ + "mixin" + ], + "type": "string" + }, + "members": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ClassField" + }, + { + "$ref": "#/definitions/ClassMethod" + } + ] + }, + "type": "array" + }, + "mixins": { + "items": { + "$ref": "#/definitions/Reference" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "parameters": { + "items": { + "$ref": "#/definitions/Parameter" + }, + "type": "array" + }, + "return": { + "properties": { + "description": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/Type" + } + }, + "type": "object" + }, + "slots": { + "description": "The shadow dom content slots that this element accepts.", + "items": { + "$ref": "#/definitions/Slot" + }, + "type": "array" + }, + "summary": { + "description": "A markdown summary suitable for display in a listing.", + "type": "string" + }, + "superclass": { + "$ref": "#/definitions/Reference" + }, + "tagName": { + "description": "An optional tag name that should be specified if this is a\nself-registering element.\n\nSelf-registering elements must also include a CustomElementExport\nin the module's exports.", + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, "Parameter": { "properties": { "default": { @@ -652,7 +727,7 @@ "type": "object" } }, - "description": "The top-level interface of a custom-elements.json file.\n\nBecause custom elements are JavaScript classes, describing a custom element\nmay require describing arbitrary JavaScript concepts like modules, classes,\nfunctions, etc. So custom-elements.json documents are capable of documenting\nthe elements in a package, as well as those JavaScript concepts.\n\nThe modules described in a package should be the public entrypoints that\nother packages may import from. Multiple modules may export the same object\nvia re-exports, but in most cases a package should document the single\ncanonical export that should be used.", + "description": "The top-level interface of a custom elements manifest file.\n\nBecause custom elements are JavaScript classes, describing a custom element\nmay require describing arbitrary JavaScript concepts like modules, classes,\nfunctions, etc. So custom elements manifests are capable of documenting\nthe elements in a package, as well as those JavaScript concepts.\n\nThe modules described in a package should be the public entrypoints that\nother packages may import from. Multiple modules may export the same object\nvia re-exports, but in most cases a package should document the single\ncanonical export that should be used.", "properties": { "modules": { "description": "An array of the modules this package contains.", @@ -666,7 +741,7 @@ "type": "string" }, "schemaVersion": { - "description": "The version of the custom-elements.json schema used in this file.", + "description": "The version of the schema used in this file.", "type": "string" } }, From f65cdffcff5b3a7c3b6dc33eaa12654fa5ae5e77 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 13 Nov 2020 18:48:19 -0800 Subject: [PATCH 7/8] Update mixin docs --- schema.json | 14 ++++++++++---- schema.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/schema.json b/schema.json index 781ef9f..4fb41f1 100644 --- a/schema.json +++ b/schema.json @@ -61,6 +61,7 @@ "type": "array" }, "mixins": { + "description": "Any class mixins applied in the extends clause of this class.\n\nIf mixins are applied in the class definition, then the true superclass\nof this class is the result of applying mixins in order to the superclass.\n\nMixins must be listed in order of their application to the superclass or\nprevious mixin application. This means that the innermost mixin is listed\nfirst. This may read backwards from the common order in JavaScript, but\nmatches the order of language used to describe mixin application, like\n\"S with A, B\".", "items": { "$ref": "#/definitions/Reference" }, @@ -74,7 +75,8 @@ "type": "string" }, "superclass": { - "$ref": "#/definitions/Reference" + "$ref": "#/definitions/Reference", + "description": "The superclass of this class.\n\nIf this class is defined with mixin\napplications, the prototype chain includes the mixin applications\nand the true superclass is computed from them." } }, "required": [ @@ -246,6 +248,7 @@ "type": "array" }, "mixins": { + "description": "Any class mixins applied in the extends clause of this class.\n\nIf mixins are applied in the class definition, then the true superclass\nof this class is the result of applying mixins in order to the superclass.\n\nMixins must be listed in order of their application to the superclass or\nprevious mixin application. This means that the innermost mixin is listed\nfirst. This may read backwards from the common order in JavaScript, but\nmatches the order of language used to describe mixin application, like\n\"S with A, B\".", "items": { "$ref": "#/definitions/Reference" }, @@ -259,7 +262,8 @@ "type": "string" }, "superclass": { - "$ref": "#/definitions/Reference" + "$ref": "#/definitions/Reference", + "description": "The superclass of this class.\n\nIf this class is defined with mixin\napplications, the prototype chain includes the mixin applications\nand the true superclass is computed from them." } }, "required": [ @@ -468,7 +472,7 @@ "type": "object" }, "MixinDeclaration": { - "description": "A description of a mixin.\n\nMixins are functions which generate a new subclass of a given superclass.\nThis interfaces describes the class and custom element features that\nare added by the mixin. As such, it extends the CustomElement interface and\nClassLike interface.\n\nSince mixins are functions, it also extends the FunctionLike interface. This\nmeans a mixin is callable, and has parameters and a return type.\n\nThe return type is often hard or impossible to accurately describe in type\nsystems like TypeScript. It requires generics and an `extends` operator\nthat TypeScript lacks. Therefore it's recommended that the return type is\nleft empty. The most common form of a mixin function takes a single\nargument, so consumers of this interface should assume that the return type\nis the single argument subclassed by this declaration.\n\nA mixin should only have a superclass if it composes another mixin, and the\nsuperclass should reference that mixin.", + "description": "A description of a class mixin.\n\nMixins are functions which generate a new subclass of a given superclass.\nThis interfaces describes the class and custom element features that\nare added by the mixin. As such, it extends the CustomElement interface and\nClassLike interface.\n\nSince mixins are functions, it also extends the FunctionLike interface. This\nmeans a mixin is callable, and has parameters and a return type.\n\nThe return type is often hard or impossible to accurately describe in type\nsystems like TypeScript. It requires generics and an `extends` operator\nthat TypeScript lacks. Therefore it's recommended that the return type is\nleft empty. The most common form of a mixin function takes a single\nargument, so consumers of this interface should assume that the return type\nis the single argument subclassed by this declaration.\n\nA mixin should not have a superclass. If a mixins composes other mixins,\nthey should be listed in the `mixins` field.", "properties": { "attributes": { "description": "The attributes that this element is known to understand.", @@ -526,6 +530,7 @@ "type": "array" }, "mixins": { + "description": "Any class mixins applied in the extends clause of this class.\n\nIf mixins are applied in the class definition, then the true superclass\nof this class is the result of applying mixins in order to the superclass.\n\nMixins must be listed in order of their application to the superclass or\nprevious mixin application. This means that the innermost mixin is listed\nfirst. This may read backwards from the common order in JavaScript, but\nmatches the order of language used to describe mixin application, like\n\"S with A, B\".", "items": { "$ref": "#/definitions/Reference" }, @@ -563,7 +568,8 @@ "type": "string" }, "superclass": { - "$ref": "#/definitions/Reference" + "$ref": "#/definitions/Reference", + "description": "The superclass of this class.\n\nIf this class is defined with mixin\napplications, the prototype chain includes the mixin applications\nand the true superclass is computed from them." }, "tagName": { "description": "An optional tag name that should be specified if this is a\nself-registering element.\n\nSelf-registering elements must also include a CustomElementExport\nin the module's exports.", diff --git a/schema.ts b/schema.ts index 341a0a6..cbeb6b0 100644 --- a/schema.ts +++ b/schema.ts @@ -361,7 +361,52 @@ export interface ClassLike { * A markdown description of the class. */ description?: string; + + /** + * The superclass of this class. + * + * If this class is defined with mixin + * applications, the prototype chain includes the mixin applications + * and the true superclass is computed from them. + */ superclass?: Reference; + + /** + * Any class mixins applied in the extends clause of this class. + * + * If mixins are applied in the class definition, then the true superclass + * of this class is the result of applying mixins in order to the superclass. + * + * Mixins must be listed in order of their application to the superclass or + * previous mixin application. This means that the innermost mixin is listed + * first. This may read backwards from the common order in JavaScript, but + * matches the order of language used to describe mixin application, like + * "S with A, B". + * + * @example + * + * ```javascript + * class T extends B(A(S)) {} + * ``` + * + * is described by: + * ```json + * { + * "kind": "class", + * "superclass": { + * "name": "S" + * }, + * "mixins": [ + * { + * "name": "A" + * }, + * { + * "name": "B" + * }, + * ] + * } + * ``` + */ mixins?: Array; members?: Array; } @@ -409,7 +454,7 @@ export interface ClassMethod extends FunctionLike { } /** - * A description of a mixin. + * A description of a class mixin. * * Mixins are functions which generate a new subclass of a given superclass. * This interfaces describes the class and custom element features that @@ -426,8 +471,8 @@ export interface ClassMethod extends FunctionLike { * argument, so consumers of this interface should assume that the return type * is the single argument subclassed by this declaration. * - * A mixin should only have a superclass if it composes another mixin, and the - * superclass should reference that mixin. + * A mixin should not have a superclass. If a mixins composes other mixins, + * they should be listed in the `mixins` field. * * @example * From 6fee0eb4ad3681082123679f566903b5f8918411 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Fri, 12 Feb 2021 10:09:41 -0800 Subject: [PATCH 8/8] Add link to mixin article --- schema.json | 2 +- schema.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/schema.json b/schema.json index 4fb41f1..55ccf0b 100644 --- a/schema.json +++ b/schema.json @@ -472,7 +472,7 @@ "type": "object" }, "MixinDeclaration": { - "description": "A description of a class mixin.\n\nMixins are functions which generate a new subclass of a given superclass.\nThis interfaces describes the class and custom element features that\nare added by the mixin. As such, it extends the CustomElement interface and\nClassLike interface.\n\nSince mixins are functions, it also extends the FunctionLike interface. This\nmeans a mixin is callable, and has parameters and a return type.\n\nThe return type is often hard or impossible to accurately describe in type\nsystems like TypeScript. It requires generics and an `extends` operator\nthat TypeScript lacks. Therefore it's recommended that the return type is\nleft empty. The most common form of a mixin function takes a single\nargument, so consumers of this interface should assume that the return type\nis the single argument subclassed by this declaration.\n\nA mixin should not have a superclass. If a mixins composes other mixins,\nthey should be listed in the `mixins` field.", + "description": "A description of a class mixin.\n\nMixins are functions which generate a new subclass of a given superclass.\nThis interfaces describes the class and custom element features that\nare added by the mixin. As such, it extends the CustomElement interface and\nClassLike interface.\n\nSince mixins are functions, it also extends the FunctionLike interface. This\nmeans a mixin is callable, and has parameters and a return type.\n\nThe return type is often hard or impossible to accurately describe in type\nsystems like TypeScript. It requires generics and an `extends` operator\nthat TypeScript lacks. Therefore it's recommended that the return type is\nleft empty. The most common form of a mixin function takes a single\nargument, so consumers of this interface should assume that the return type\nis the single argument subclassed by this declaration.\n\nA mixin should not have a superclass. If a mixins composes other mixins,\nthey should be listed in the `mixins` field.\n\nSee [this article]{@link https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/}\nfor more information on the classmixin pattern in JavaScript.", "properties": { "attributes": { "description": "The attributes that this element is known to understand.", diff --git a/schema.ts b/schema.ts index cbeb6b0..1c1883d 100644 --- a/schema.ts +++ b/schema.ts @@ -474,6 +474,9 @@ export interface ClassMethod extends FunctionLike { * A mixin should not have a superclass. If a mixins composes other mixins, * they should be listed in the `mixins` field. * + * See [this article]{@link https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/} + * for more information on the classmixin pattern in JavaScript. + * * @example * * This JavaScript mixin declaration: