

Pages
XML Schema
This page describes the full details about defining interfaces and functions in Typal XML. For each use case, we list the usage pattern in XML, then display what typedefs are generated for the IDE, and finally show what externs for the compiler are printed.
Root Tag
The root tag always has to be <types> with a namespace in the ns attribute.
<types ns="com.example">
<!-- ... -->
</types>
The use of namespaces is compulsory to organise the source code better and allow for future packaging and distribution, aligned with standard practices in other programming languages. Sometimes, the package name on NPM prefixed with an underscore _ (e.g., _myPackage) can be used: the underscore will make sure that there is no variable overlap as namespaces' declarations will always be placed in externs.
Interfaces
To denote an interface, its name is used as a tag itself. All interfaces must start with an I, followed by a word that best describe objects of a particular class or behaviour if the interface is used as a trait.
<types ns="_typal">
<ILogger>
<method name="log" void>Prints to stdout.</method>
<method name="warn" void>Prints to stderr.</method>
Provides functionality to print messages to the console.
</ILogger>
<IIncremental basic>
<method name="increment" void>Increases the internal var.</method>
A trait that allows to increment the value.
</IIncremental>
</types>
/** @nocompile */
/** */
var _typal = {}
/**
* Provides functionality to print messages to the console.
* @interface _typal.ILogger
*/
_typal.ILogger = class { }
/**
* Prints to stdout.
*/
_typal.ILogger.prototype.log = function() {}
/**
* Prints to stderr.
*/
_typal.ILogger.prototype.warn = function() {}
/**
* A concrete class of _ILogger_ instances.
* @constructor _typal.Logger
* @implements {_typal.ILogger} Provides functionality to print messages to the console.
*/
_typal.Logger = class extends _typal.ILogger { }
_typal.Logger.prototype.constructor = _typal.Logger
/**
* A trait that allows to increment the value.
* @interface _typal.IIncremental
*/
_typal.IIncremental = class { }
/**
* Increases the internal var.
*/
_typal.IIncremental.prototype.increment = function() {}
For each interface, a constructor will be created automatically, unless the basic attribute is added in which case the interface is recognised as design-only such that its instances cannot be instantiated. This can be helpful for pure design concepts in modelling.
Extending Interfaces
Interfaces can extend other interfaces with the <implements> tag. Design-wise, this allows to decompose functionality by traits which are then composed together, which is extremely beneficial for comprehension as components can be kept small and concise.
<types ns="_typal">
<ILogger>
<implements>IWarner</implements>
<implements>INotifier</implements>
Provides functionality to print notifications and warnings.
</ILogger>
<IWarner on="ILogger" basic>
<method name="warn" void>Prints to stderr.</method>
<static name="WARN" void>Prints to stderr (static).</static>
A trait for warnings.
</IWarner>
<INotifier on="ILogger" basic>
<method name="notify" void>Sends a message to message queue.</method>
<static name="NOTIFY" void>
Sends a message to message queue (static).
</static>
A trait for notifying.
</INotifier>
</types>
/** @nocompile */
/** */
var _typal = {}
/** @typedef {function(new: _typal.IWarner&_typal.INotifier)} _typal.ILogger.constructor */
/** @typedef {typeof _typal.IWarner} _typal.IWarner.typeof */
/** @typedef {typeof _typal.INotifier} _typal.INotifier.typeof */
/**
* Provides functionality to print notifications and warnings.
* @interface _typal.ILogger
*/
_typal.ILogger = class extends /** @type {_typal.ILogger.constructor&_typal.IWarner.typeof&_typal.INotifier.typeof} */ (class {}) { }
_typal.ILogger.prototype.constructor = _typal.ILogger
/**
* A concrete class of _ILogger_ instances.
* @constructor _typal.Logger
* @implements {_typal.ILogger} Provides functionality to print notifications and warnings.
*/
_typal.Logger = class extends _typal.ILogger { }
_typal.Logger.prototype.constructor = _typal.Logger
/**
* A trait for warnings.
* @interface _typal.IWarner
*/
_typal.IWarner = class { }
/**
* Prints to stderr (static).
*/
_typal.IWarner.WARN = function() {}
/**
* Prints to stderr.
*/
_typal.IWarner.prototype.warn = function() {}
/**
* A trait for notifying.
* @interface _typal.INotifier
*/
_typal.INotifier = class { }
/**
* Sends a message to message queue (static).
*/
_typal.INotifier.NOTIFY = function() {}
/**
* Sends a message to message queue.
*/
_typal.INotifier.prototype.notify = function() {}
When the interfaces are extended with other interfaces, 2 things will happen: first, an additional .constructor typedef will be generated (function(new: _typal.IWarner&_typal.INotifier)) and a .typeof typedef will be created for each of the extended parts.
The first construct combines interfaces together as a clever trick using function(new: A&B[...&N]) so that their methods and fields are now recognised as one class when referenced in the source code:
And secondly, Typal puts together .constructor with .typeof's together, to make sure that the static methods are composed together correctly.
As you can see, Typal has done all of the hard work of ensuring that the developer experience is of appropriate standard, while the compiler is still able to perform type-checking.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @interface */
_typal.IWarner = function() {}
/** @return {void} */
_typal.IWarner.WARN = function() {}
/** @return {void} */
_typal.IWarner.prototype.warn = function() {}
/** @interface */
_typal.INotifier = function() {}
/** @return {void} */
_typal.INotifier.NOTIFY = function() {}
/** @return {void} */
_typal.INotifier.prototype.notify = function() {}
/**
* @interface
* @extends {_typal.IWarner}
* @extends {_typal.INotifier}
*/
_typal.ILogger = function() {}
/**
* @constructor
* @implements {_typal.ILogger}
*/
_typal.Logger = function() {}
Extending Constructors
Occasionally, we'll find ourselves extending existing web APIs which provide constructors but not interfaces. Since there's only one extends hook for extension of constructors in JavaScript source code, only a single constructor can be specified with the <constructor-extends> tag.
<types ns="_typal">
<IMyEvent>
<constructor-extends>Event</constructor-extends>
<prop name="myData">
Some data associated with the event.
</prop>
My event extends an event with a `myData` field.
</IMyEvent>
</types>
/** @nocompile */
/** */
var _typal = {}
/**
* My event extends an event with a `myData` field.
* @interface _typal.IMyEvent
*/
_typal.IMyEvent = class { }
/**
* Some data associated with the event.
*/
_typal.IMyEvent.prototype.myData = /** @type {*} */ (void 0)
/** @typedef {function(new: Event&_typal.IMyEvent)} _typal.MyEvent.constructor */
/** @typedef {typeof _typal.IMyEvent} _typal.IMyEvent.typeof */
/**
* A concrete class of _IMyEvent_ instances.
* @constructor _typal.MyEvent
* @implements {_typal.IMyEvent} My event extends an event with a `myData` field.
*/
_typal.MyEvent = class extends /** @type {_typal.MyEvent.constructor&_typal.IMyEvent.typeof} */ (class {}) { }
_typal.MyEvent.prototype.constructor = _typal.MyEvent
The support for IDE hints is limited, though, as we won't be able to receive autocompletion suggestions of parent supertype when referencing interfaces (e.g., /** type {_typal.IMyEvent} */(ev)), and we will have to work with constructors (e.g., use /** type {_typal.MyEvent} */(ev)) instead. This is a limitation of the fact that the web platform was not built with interfaces in mind, which is a shame.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @interface */
_typal.IMyEvent = function() {}
/** @type {*} */
_typal.IMyEvent.prototype.myData
/**
* @constructor
* @implements {_typal.IMyEvent}
* @extends {Event}
*/
_typal.MyEvent = function() {}
Generics
Generics is fully supported by Typal: the parameters are passed using <template> tags, and can be referenced in fields, methods and extended interfaces.
<types ns="_typal">
<IPlayer>
<implements>ITeamPlayer<SPORTS></implements>
<template name="TEAM" />
<template name="SPORTS" bound="ISports" />
<method name="train" async>
<arg name="team" type="TEAM">
The team instance.
</arg>
<return type="SPORTS" />
Start training with the team.
</method>
A team player of a team sports.
</IPlayer>
</types>
/** @nocompile */
/** */
var _typal = {}
/** @typedef {function(new: ITeamPlayer<SPORTS>)} _typal.IPlayer.constructor */
/** @typedef {typeof ITeamPlayer} ITeamPlayer.typeof */
/**
* A team player of a team sports.
* @interface _typal.IPlayer
* @template TEAM
* @template SPORTS
*/
_typal.IPlayer = class extends /** @type {_typal.IPlayer.constructor&ITeamPlayer.typeof} */ (class {}) {
/**
* Start training with the team.
* @param {TEAM} team The team instance.
*/
async train(team) {
return /** @type {!Promise<SPORTS>} */ (void 0)
}
}
_typal.IPlayer.prototype.constructor = _typal.IPlayer
/**
* A concrete class of _IPlayer_ instances.
* @constructor _typal.Player
* @implements {_typal.IPlayer<TEAM, SPORTS>} A team player of a team sports.
* @template TEAM
* @template SPORTS
* @extends {_typal.IPlayer<TEAM,SPORTS>}
*/
_typal.Player = class extends _typal.IPlayer { }
_typal.Player.prototype.constructor = _typal.Player
There's a possibility to add a bound attribute to the template tag, however at the moment it does nothing, as bound generics is not supported by Closure and will actually result in an error.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/**
* @interface
* @extends {ITeamPlayer<SPORTS>}
* @template TEAM
* @template SPORTS
*/
_typal.IPlayer = function() {}
/**
* @param {TEAM} team
* @return {!Promise<SPORTS>}
*/
_typal.IPlayer.prototype.train = function(team) {}
/**
* @constructor
* @implements {_typal.IPlayer<TEAM, SPORTS>}
* @template TEAM
* @template SPORTS
*/
_typal.Player = function() {}
Constructors
All of the web APIs available to the browser were not designed against interfaces but against actual constructors (i.e., we don't program to IIntersectionObserver interface but to IntersectionObserver constructor). Although this implementation-centric approach violates the separation of concerns between software design and programming, there's a possibility to simply add constructors without interfaces using a <constructor> tag that would allow to retroject Typal specification into existing infrastructure.
<types>
<constructor name="IntersectionObserver">
<method name="constructor">
<cb name="cb" void>
<param name="entries" type="!Array<IntersectionObserverEntry>">
The list of entries that started or stopped intersecting
with the viewport.
</param>
The callback for the observed element.
</cb>
Creates a new observer instance.
</method>
<method name="observe" void>
<arg name="el" type="!HTMLElement" >An element to observe.</arg>
Starts observing the element.
</method>
Allows to track the position of elements relative to viewport.
</constructor>
<record name="IntersectionObserverEntry">
<prop name="boundingClientRect" />
<number name="intersectionRatio" />
<prop name="intersectionRect" />
<bool name="isIntersecting" />
<prop name="rootBounds" />
<prop name="target" />
<number name="time" />
A single observed item.
</record>
</types>
/** @nocompile */
/**
* Allows to track the position of elements relative to viewport.
* @constructor IntersectionObserver
*/
var IntersectionObserver = class {
/**
* Creates a new observer instance.
* @param {(entries: !Array<IntersectionObserverEntry>) => void} cb The callback for the observed element.
*/
constructor(cb) { }
}
/**
* Starts observing the element.
* @param {!HTMLElement} el An element to observe.
*/
IntersectionObserver.prototype.observe = function(el) {}
/**
* @typedef {Object} IntersectionObserverEntry A single observed item.
* @prop {*} boundingClientRect
* @prop {number} intersectionRatio
* @prop {*} intersectionRect
* @prop {boolean} isIntersecting
* @prop {*} rootBounds
* @prop {*} target
* @prop {number} time
*/
We do recommend using interfaces, though, as constructors will be made for them automatically and there's no drawbacks to taking advantage of the Typal design model.
/**
* @fileoverview
* @externs
*/
/**
* @constructor
* @param {function(!Array<IntersectionObserverEntry>): void} cb
*/
var IntersectionObserver = function(cb) {}
/**
* @param {!HTMLElement} el
* @return {void}
*/
IntersectionObserver.prototype.observe = function(el) {}
/** @typedef {{ boundingClientRect: *, intersectionRatio: number, intersectionRect: *, isIntersecting: boolean, rootBounds: *, target: *, time: number }} */
var IntersectionObserverEntry
Records
Records are objects without a state that conform to a certain structure. They are defined with a record tag.
<types ns="_typal">
<record name="SourceCodeLocation">
<number name="startLine" />
<number name="startColumn" />
<number name="endLine" />
<number name="endColumn" />
<bool name="ephemeral" opt />
<string name="file">The file.</string>
Encodes an area of source code.
</record>
</types>
In typedefs, the @record tag is used for a record.
/** @nocompile */
/**
* @typedef {Object} _typal.SourceCodeLocation Encodes an area of source code.
* @prop {number} startLine
* @prop {number} startColumn
* @prop {number} endLine
* @prop {number} endColumn
* @prop {string} file The file.
* @prop {boolean} [ephemeral]
*/
In externs, the @typedef tag is also used for a record. Optional fields's types will be automatically joined with |undefined value.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @typedef {{ startLine: number, startColumn: number, endLine: number, endColumn: number, ephemeral: (boolean|undefined), file: string }} */
_typal.SourceCodeLocation
Structural Interfaces
Closure Compiler supports a special @record type which represents an interface whose type checking is based on the structure of the object (presence and type of fields), rather than the inheritance-chain conformance.
We use the <constructor> tag with a struct attribute to encode structural interfaces.
<types ns="_typal">
<constructor name="MyElement" struct>
<field name="classes" type="!Set<string>" opt>
The classes of the element.
</field>
<field name="styles" type="!Map<string, string>" opt>
The styles found on the element.
</field>
An element with classes and styles.
</constructor>
</types>
In typedefs, structs are printed as normal interfaces, although they do enjoy a @record tag added to them. The difference with the typedefs from the previous section, is that props of typedefs can be optional, while fields of an structural interface cannot be optional (as they are found on a class) - although their type can be undefined.
/** @nocompile */
/** */
var _typal = {}
/**
* An element with classes and styles.
* @record _typal.MyElement
*/
_typal.MyElement = class { }
/**
* The classes of the element.
*/
_typal.MyElement.prototype.classes = /** @type {(!Set<string>)|undefined} */ (void 0)
/**
* The styles found on the element.
*/
_typal.MyElement.prototype.styles = /** @type {(!Map<string, string>)|undefined} */ (void 0)
In externs, the structs are printed using standard class notation (via .prototype) and have the @record tag to let the compiler know that objects of these types should not be checked for typal covariance. It might be confusing that we previously called typedefs records and the compiler uses the @record tag here, but those are different things.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @record */
_typal.MyElement = function() {}
/** @type {(!Set<string>)|undefined} */
_typal.MyElement.prototype.classes
/** @type {(!Map<string, string>)|undefined} */
_typal.MyElement.prototype.styles
Functions
In addition to interfaces and the rest of OOP concepts, Typal IDL can be used to encode functions as first-class citizens, too. It is done with the <function> tag that accepts arguments and a return type, as well as generics.
<types ns="_typal">
<function name="trackElements" async>
<template name="ELEMENT" />
<arg name="offset" number>
The Y-offset from which to start tracking.
</arg>
<arg name="...els" type="ELEMENT">
The elements to track.
</arg>
<return type="Tracker<ELEMENT>" />
Tracks the position of elements of the page.
</function>
<function name="indent" string>
<arg name="string" string>
The string to indent.
</arg>
<arg name="ws" string opt>
The whitespace to prepend to each line.
</arg>
Indent a string with new lines.
</function>
<function name="testString">
<arg name="maybeString" string default="a_string">
The string to check.
</arg>
<return string opt>
If the argument is a string, returns it.
</return>
Checks whether the passed value is a string.
</function>
</types>
but if the description of the return is desired, the return tag should be used;
Here's the list of rules to defining functions:
- Functions can be asynchronous in which case their return will be of generic Promise type;
- Rest arguments need to have their name start with ...;
- The return type can be set to string|number|boolean type by using the attribute on the function tag itself, but if the description of the return is desired, the return tag should be used;
- Arguments can be set to string|number|boolean type by using the attribute, otherwise the type= attribute has to be defined;
- The default value for an argument can be given with the default= attribute;
- Arguments can be made optional with the opt attribute;
- A return can be made optional with the opt attribute.
/** @nocompile */
/** */
var _typal = {}
/** @typedef {typeof _typal.trackElements} */
/**
* Tracks the position of elements of the page.
* @param {number} offset The Y-offset from which to start tracking.
* @param {...ELEMENT} els The elements to track.
* @return {!Promise<Tracker<ELEMENT>>}
* @template ELEMENT
*/
_typal.trackElements = function(offset, ...els) {}
/** @typedef {typeof _typal.indent} */
/**
* Indent a string with new lines.
* @param {string} string The string to indent.
* @param {string} [ws] The whitespace to prepend to each line.
* @return {string}
*/
_typal.indent = function(string, ws) {}
/** @typedef {typeof _typal.testString} */
/**
* Checks whether the passed value is a string.
* @param {string} [maybeString="a_string"] The string to check. Default `a_string`.
* @return {void|string} If the argument is a string, returns it.
*/
_typal.testString = function(maybeString) {}
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @const */
var $$_typal = {}
/**
* @param {number} offset
* @param {...ELEMENT} els
* @return {!Promise<Tracker<ELEMENT>>}
* @template ELEMENT
*/
$$_typal.trackElements = function(offset, ...els) {}
/** @typedef {typeof $$_typal.trackElements} */
_typal.trackElements
/** @typedef {function(string, string=): string} */
_typal.indent
/** @typedef {function(string=): (void|string)} */
_typal.testString
Usage in Source
For Closure, functions will be encoded as typedefs. When used with generics, a proxy function will be created. This is because the compiler does not will not recognise functions in externs as global types to be used with source code @type annotations (see below).
To be able to define a function in source as of the designated function type, the @type JSDoc tag needs to be placed above a function:
/** @type {_typal.trackElements} */
export function trackElements(offset,...elements) {
test(offset)
}
/**
* @param {string} s
*/
function test(s) {}
To validate that we have to use the typedef tag when working with functions this way, let's perform a sanity check by attempting using the actual function found on the $$_typal namespace:
/** @type {$$_typal.trackElements} */
export function trackElements(offset,...elements) {
test(offset)
}
What happens is that we receive the following warning and no type-checking on arguments or return type of the function, because the compiler does not recognise it:
pages/xml/chunks/functions/functions.js:1:11: WARNING - [JSC_UNRECOGNIZED_TYPE_ERROR] Bad type annotation. Unknown type $$_typal.trackElements
1| /** @type {$$_typal.trackElements} */
^
0 error(s), 1 warning(s), 87.5% typed
However, if we restore the source code back to the normal _typal namespace, we do receive the following warnings, indicating that our strategy works:
pages/xml/chunks/functions/functions.js:2:7: WARNING - [JSC_MISSING_RETURN_STATEMENT] Missing return statement. Function expected to return Promise<(Tracker|null)>.
2| export function trackElements(offset,...elements) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3| test(offset)
^^^^^^^^^^^^^^
4| }
^
pages/xml/chunks/functions/functions.js:3:7: WARNING - [JSC_TYPE_MISMATCH] actual parameter 1 of test$$module$pages$xml$chunks$functions$functions does not match formal parameter
found : number
required: string
3| test(offset)
^^^^^^
0 error(s), 2 warning(s), 100.0% typed
The first warning is due to the fact that our functions is not async, as designed in the IDL, while the compiler expected a return type of Promise. And the second warning shows that the compiler has successfully inferred the argument type from the @type annotation above the function. Be mindful that this technique works with exported functions only (either default or named).
VSCode will also enrich the the function with appropriate types for the in-IDE experience, without us having to give the function any @param and @return tags — all the design info is now completely moved into the IDL, so that we can focus on clean coding.
Callbacks
To implement a callback to a function or a method, a cb tag can be applied (nested callbacks are not supported right now — to overcome this limitation, a standalone function can be defined instead under the root tag).
<types ns="_typal">
<function name="observeElement" void>
<arg name="element" type="!HTMLElement">
The element to observe.
</arg>
<cb name="onObservation" void>
<param name="topLeftCorner" type="Corner">
The coordinates of the top-left corner.
</param>
<param name="bottomRightCorner" type="Corner">
The coordinates of the bottom-right corner.
</param>
A callback to fire when the element becomes visible.
</cb>
Start observing an element.
</function>
<record name="Corner">
<number name="x" />
<number name="y" />
The coordinates set.
</record>
</types>
Arguments to callbacks must be called a <param>.
/** @nocompile */
/** */
var _typal = {}
/**
* @typedef {Object} _typal.Corner The coordinates set.
* @prop {number} x
* @prop {number} y
*/
/** @typedef {typeof _typal.observeElement} */
/**
* Start observing an element.
* @param {!HTMLElement} element The element to observe.
* @param {(topLeftCorner: _typal.Corner, bottomRightCorner: _typal.Corner) => void} onObservation A callback to fire when the element becomes visible.
* @return {void}
*/
_typal.observeElement = function(element, onObservation) {}
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @typedef {function(!HTMLElement, function(_typal.Corner, _typal.Corner): void): void} */
_typal.observeElement
/** @typedef {{ x: number, y: number }} */
_typal.Corner
Fields and Methods
The fields can be specified on interfaces using the string, number or bool tags. If the type is complex, the field should be used instead. When working with records, the prop should be used instead of field, as field is an OOP concept while property is a general concept of objects.
To show that a field's type is nullable, either the nullable attribute can supplied, or the ? symbol prepended to the field's type. And same works in reverse: add non-nullable to denote non-nullable fields, or prepend the ! operator to their type. For example, there's an Attrs record which is by default non-nullable due to it being defined as @typedef in externs, but by adding the nullable attribute, we claim that it can be of type null.
<types ns="_typal">
<IElement>
<template name="HTML">
The HTML component type wrapped by the element.
</template>
<string name="templateName">
Returns the name of the block for rendering.
</string>
<number name="padding" default="2">
The padding to apply as whitespace.
</number>
<bool name="root" def>
Whether this is a wrapper for `document` element.
</bool>
<field name="children" type="!Array<!IElement<HTML>>">
The recursive list of children for the node.
</field>
<field name="attributes" nullable type="Attrs">
The list of attributes. Set to `null` when
the template does not support attributes.
</field>
<getter name="html" type="HTML">
The wrapped _HTML_ component.
</getter>
<method name="constructor">
<arg name="html" type="HTML">
The HTML component.
</arg>
<cb name="onObservation" opt void>
<param name="topCorner" type="{x:number,y:number}">
The coordinates of the top corner.
</param>
A callback to fire when the element becomes visible.
</cb>
Creates a new element from an HTML component.
</method>
<method async name="generateBlock">
<return string>A generated HTML block.</return>
Reads the template using the `templateName` and
fills in the values.
</method>
A single visual element for rendering.
</IElement>
<record name="Attrs">
Some attributes.
</record>
</types>
/** @nocompile */
/** */
var _typal = {}
/**
* A single visual element for rendering.
* @interface _typal.IElement
* @template HTML The HTML component type wrapped by the element.
*/
_typal.IElement = class {
constructor() {
/**
* The recursive list of children for the node.
* @type {!Array<!_typal.IElement<HTML>>}
*/
this.children
/**
* The wrapped _HTML_ component.
* @type {HTML}
*/
this.html
}
}
/**
* Returns the name of the block for rendering.
*/
_typal.IElement.prototype.templateName = /** @type {string} */ (void 0)
/**
* The padding to apply as whitespace. Default `2`.
*/
_typal.IElement.prototype.padding = /** @type {number|undefined} */ (void 0)
/**
* Whether this is a wrapper for `document` element. Default `false`.
*/
_typal.IElement.prototype.root = /** @type {boolean|undefined} */ (void 0)
/**
* The list of attributes. Set to `null` when
* the template does not support attributes.
*/
_typal.IElement.prototype.attributes = /** @type {?_typal.Attrs} */ (void 0)
/**
* Reads the template using the `templateName` and
* fills in the values.
* @return {!Promise<string>} A generated HTML block.
*/
_typal.IElement.prototype.generateBlock = function() {}
/**
* A concrete class of _IElement_ instances.
* @constructor _typal.Element
* @implements {_typal.IElement<HTML>} A single visual element for rendering.
* @template HTML The HTML component type wrapped by the element.
* @extends {_typal.IElement<HTML>}
*/
_typal.Element = class extends _typal.IElement {
/**
* Creates a new element from an HTML component.
* @param {HTML} html The HTML component.
* @param {(topCorner: { x: number, y: number }) => void} [onObservation] A callback to fire when the element becomes visible.
*/
constructor(html, onObservation) {
super(html, onObservation)
}
}
_typal.Element.prototype.constructor = _typal.Element
/** @typedef {typeof __$te_plain} _typal.Attrs Some attributes. */
To indicate that a field cannot be set, the <getter> tag should be used instead of the <field> tag. It has no effect in typedefs or externs, but might make a difference for code generation in other Typal-related software.
The methods follow the same rules as functions, and must be added with the <method> tag. The constructor is just a special method, however because interfaces is a design concept (i.e., we cannot instantiate *new IInterface, only new Implementation*), Typal will not print arguments to interfaces' constructor methods.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/**
* @interface
* @template HTML
*/
_typal.IElement = function() {}
/** @type {string} */
_typal.IElement.prototype.templateName
/** @type {number|undefined} */
_typal.IElement.prototype.padding
/** @type {boolean|undefined} */
_typal.IElement.prototype.root
/** @type {!Array<!_typal.IElement<HTML>>} */
_typal.IElement.prototype.children
/** @type {?_typal.Attrs} */
_typal.IElement.prototype.attributes
/** @type {HTML} */
_typal.IElement.prototype.html
/** @return {!Promise<string>} */
_typal.IElement.prototype.generateBlock = function() {}
/**
* @constructor
* @param {HTML} html
* @param {function({ x: number, y: number }): void} [onObservation]
* @implements {_typal.IElement<HTML>}
* @template HTML
*/
_typal.Element = function(html, onObservation) {}
/** @typedef {typeof __$te_plain} */
_typal.Attrs
We can check that Closure does not check for type conformance of constructors to their interfaces' constructor functions (as well as between interfaces' constructors) with the following snippet:
/**
* @fileoverview
* @externs
*/
/**
* @interface
* @param {number} number
* @param {number} number2
*/
_typal.IElement0 = function(number, number2) {}
/**
* @interface
* @param {string} string
* @param {string} string2
* @extends {_typal.IElement0}
*/
_typal.IElement = function(string, string2) {}
/**
* @constructor
* @param {number} number
* @param {function({ x: number, y: number }): void} [onObservation]
* @implements {_typal.IElement<HTML>}
* @template HTML
*/
_typal.Element = function(number, onObservation) {}
After compiling, the compiler won't issue any warnings about incompatibility between the argument types of the _typal.Element @constructor against its interface (or between _typal.IElement0 whose arguments are strings and _typal.IElement whose arguments are numbers), therefore Typal will always omit printing params on interfaces, as interfaces only represent design types, while a constructor is an implementation concept.
On the other hand, some warnings that the compiler will issue are the following:
pages/xml/chunks/elements/typedefs.js:11:43: WARNING - [JSC_TYPE_PARSE_ERROR] Bad type annotation. missing closing ) See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler for more information.
pages/xml/chunks/elements/typedefs.js:60:23: WARNING - [JSC_TYPE_PARSE_ERROR] Bad type annotation. missing closing ) See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler for more information.
This is in regards to typedefs and the VSCode callback syntax that is not understood by the compiler. Despite the fact we'd added the @nocompile tag at the top of the JS typedefs file, Closure is still attempting to parse its contents, although it will have no effect on the result of compilation. These warnings are suppressed using --hide-warnings-for types/typedefs.js CLI flag.
Arcs and Recurns
In it quite often in the JS world that methods receive records as arguments: this makes it easier to add new options later on without sacrificing readability or breaking API contracts, as the order of such named parameters becomes unimportant. Typal acknowledges this idiomatic JavaScript pattern and allows to specify a special kind of argument, known as an arc (argument record) right in the body of the function or method.
The same principle applies to complex return types: if a function or method wishes to return more than 1 value, they can do it with the <recurn> tag (return record). This helps to avoid cluttering of the XML files by many records that are only used in one place as return types of particular methods.
<types ns="_typal">
<IStylesheets basic>
<method name="compileStylesheets" async>
<arg name="stylesheets" type="!Array<string>">
The paths to the stylesheets to compile.
</arg>
<arc name="opts" extends="GeneralOptions" opt>
<string name="rootSelector" opt />
<string name="cssRenamingPrefix" opt />
<bool name="preserveImportantComments" opt />
Optional parameters for the method.
</arc>
<recurn>
<string name="stylesheet" opt>
The compiled stylesheet. Undefined if an error was thrown.
</string>
<string name="sourceMap" opt>
The source map.
</string>
<prop name="warnings" type="!Array<{
line:number,col:number,message:string
}>">
The list of warnings.
</prop>
<prop name="error" type="Error">
If the stylesheets compiler failed, returns the error here,
otherwise is set to `null`.
</prop>
</recurn>
Allows to compile stylesheets.
</method>
An interface to the Closure™ _Stylesheets_.
</IStylesheets>
<record name="GeneralOptions">
<string name="jar" opt>
The path to the stylesheets JAR file.
</string>
Shared options for the stylesheets compiler.
</record>
</types>
/** @nocompile */
/** */
var _typal = {}
/**
* An interface to the Closure™ _Stylesheets_.
* @interface _typal.IStylesheets
*/
_typal.IStylesheets = class { }
/**
* Allows to compile stylesheets.
* @param {!Array<string>} stylesheets The paths to the stylesheets to compile.
* @param {!_typal.IStylesheets.compileStylesheets.Opts} [opts] Optional parameters for the method.
* - `[rootSelector]` _string?_
* - `[cssRenamingPrefix]` _string?_
* - `[preserveImportantComments]` _boolean?_
* - `[jar]` _string?_ The path to the stylesheets JAR file. ⤴ *GeneralOptions*
* @return {!Promise<_typal.IStylesheets.compileStylesheets.Return>}
*/
_typal.IStylesheets.prototype.compileStylesheets = function(stylesheets, opts) {}
/**
* @typedef {Object} $_typal.IStylesheets.compileStylesheets.Opts
* @prop {string} [rootSelector]
* @prop {string} [cssRenamingPrefix]
* @prop {boolean} [preserveImportantComments]
*/
/** @typedef {$_typal.IStylesheets.compileStylesheets.Opts&_typal.GeneralOptions} _typal.IStylesheets.compileStylesheets.Opts Optional parameters for the method. */
/**
* @typedef {Object} _typal.IStylesheets.compileStylesheets.Return
* @prop {!Array<{ line: number, col: number, message: string }>} warnings The list of warnings.
* @prop {Error} error If the stylesheets compiler failed, returns the error here,
* otherwise is set to `null`.
* @prop {string} [stylesheet] The compiled stylesheet. Undefined if an error was thrown.
* @prop {string} [sourceMap] The source map.
*/
/**
* @typedef {Object} _typal.GeneralOptions Shared options for the stylesheets compiler.
* @prop {string} [jar] The path to the stylesheets JAR file.
*/
Typal will create a standalone type based on the function name (or the name of the interface and method) where the arc was used.
Arcs and recurns can also extend records defined elsewhere with the extend attribute. When expanding the documentation above methods, the source record of the property will be indicated (if it was loaded via XML) after the ⤴ symbol.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @interface */
_typal.IStylesheets = function() {}
/**
* @param {!Array<string>} stylesheets
* @param {!_typal.IStylesheets.compileStylesheets.Opts} [opts]
* @return {!Promise<_typal.IStylesheets.compileStylesheets.Return>}
*/
_typal.IStylesheets.prototype.compileStylesheets = function(stylesheets, opts) {}
/** @typedef {{ jar: (string|undefined) }} */
_typal.GeneralOptions
/**
* @record
* @extends {_typal.GeneralOptions}
*/
_typal.IStylesheets.compileStylesheets.Opts = function() {}
/** @type {string|undefined} */
_typal.IStylesheets.compileStylesheets.Opts.prototype.rootSelector
/** @type {string|undefined} */
_typal.IStylesheets.compileStylesheets.Opts.prototype.cssRenamingPrefix
/** @type {boolean|undefined} */
_typal.IStylesheets.compileStylesheets.Opts.prototype.preserveImportantComments
/** @record */
_typal.IStylesheets.compileStylesheets.Return = function() {}
/** @type {string|undefined} */
_typal.IStylesheets.compileStylesheets.Return.prototype.stylesheet
/** @type {string|undefined} */
_typal.IStylesheets.compileStylesheets.Return.prototype.sourceMap
/** @type {!Array<{ line: number, col: number, message: string }>} */
_typal.IStylesheets.compileStylesheets.Return.prototype.warnings
/** @type {Error} */
_typal.IStylesheets.compileStylesheets.Return.prototype.error
Enums
Typal supports string enums for better developer productivity: VSCode IDE will allow to pick a value from the list of predefined strings, speeding up coding significantly and making code less error-prone. We don't make use of actual @enum annotation for the compiler, but provide means to lock the choice of values.
<types ns="_typal">
<enum name="OutputRenamingMapFormat">
<choice val="JSON">
Handles mappings as a plain JSON object.
</choice>
<choice val="CLOSURE_COMPILED">
A format suitable for compatibility with the `goog.setCssNameMapping()`
method.
</choice>
<choice val="CLOSURE_COMPILED_BY_WHOLE">
Handles mappings as they have been renamed, which allows hyphens.
</choice>
<choice val="CLOSURE_COMPILED_SPLIT_HYPHENS">
Splits mappings by hyphen.
</choice>
<choice val="CLOSURE_UNCOMPILED">
Assigns to or reads mapping from the `CLOSURE_CSS_NAME_MAPPING` global
variable.
</choice>
<choice val="PROPERTIES">
Uses the `.properties` file format.
</choice>
<choice val="JSCOMP_VARIABLE_MAP">
Legacy map format used by the Compiler.
</choice>
These are the possible values to be passed in `--output-renaming-map-format`
or `--input-renaming-map-format` flags to _Closure™ Stylesheets_.
</enum>
</types>
/** @nocompile */
/**
* @typedef {'JSON'|'CLOSURE_COMPILED'|'CLOSURE_COMPILED_BY_WHOLE'|'CLOSURE_COMPILED_SPLIT_HYPHENS'|'CLOSURE_UNCOMPILED'|'PROPERTIES'|'JSCOMP_VARIABLE_MAP'} _typal.OutputRenamingMapFormat These are the possible values to be passed in `--output-renaming-map-format`
* or `--input-renaming-map-format` flags to _Closure™ Stylesheets_.
* Can be either:
* - _JSON_: handles mappings as a plain JSON object.
* - _CLOSURE_COMPILED_: a format suitable for compatibility with the `goog.setCssNameMapping()`
* method.
* - _CLOSURE_COMPILED_BY_WHOLE_: handles mappings as they have been renamed or which allows hyphens.
* - _CLOSURE_COMPILED_SPLIT_HYPHENS_: splits mappings by hyphen.
* - _CLOSURE_UNCOMPILED_: assigns to or reads mapping from the `CLOSURE_CSS_NAME_MAPPING` global
* variable.
* - _PROPERTIES_: uses the `.properties` file format.
* - _JSCOMP_VARIABLE_MAP_: legacy map format used by the Compiler.
*/
The enum can then be used as an advanced string type, with the IDE providing expansions into the list of possible values. Sadly, VSCode will not show descriptions of the choices.
/**
* @fileoverview
* @externs
*/
/** @const */
var _typal = {}
/** @typedef {string} */
_typal.OutputRenamingMapFormat