runem / web-component-analyzer Goto Github PK
View Code? Open in Web Editor NEWCLI that analyzes web components and emits documentation
Home Page: https://runem.github.io/web-component-analyzer
License: MIT License
CLI that analyzes web components and emits documentation
Home Page: https://runem.github.io/web-component-analyzer
License: MIT License
hey,
it would be really nice to have types/typeHints for css properties (you will need to hand write them). These types can then be used to either link to documentation or use them to display color pickers 💪
Types I would see so far:
Stahman proposal:
/**
* This is a container looking like a card with a back and front side you can switch
*
* @cssprop --demo-wc-card-header-font-size <Length> - Header font size
* @cssprop --demo-wc-card-front-color <Color> - Font color for front
*/
export class DemoWcCard extends LitElement {}
output in customElements.json
:
// tags.cssProperties[
{
"name": "--demo-wc-card-font-front",
"description": "Font color for front",
"type": "Color"
}
Your docs say to do npm install -g web-component-analyer
. The 'z' is missing. It should read npm install -g web-component-analyzer
😄
It looks like the JSON output is missing the cssProp/cssProperty declarations from the jsdoc.
For programmatic use, one of the simplest APIs I can think of would be to analyze a TypeScript project either by folder, or by reference to tsconfig.json file. The entrypoint, rather than the caller, would create the TypeScript program and checker.
In order to use an element you need to know what module to import. The custom-elements.json output doesn't include this at the moment.
https://github.com/bahrus/xtal-frappe-chart/blob/master/custom-elements.json was generated from web-component-analyzer. Default value generation is greatly appreciated. But arrays with negative numbers are turned into nulls.
Components that are written with array input do not out-put the programmed default values in the properties table.
class MyComp extends LitElement {
/**
* should render default values
*/
@property({
type: Array
})
myArray = ["value"]
}
Property | Attribute | Type | Default | Description |
---|---|---|---|---|
myArray |
myArray |
string[] |
["value"] | should render default values |
This issue follows up on #125 (comment)
/**
* Whether or not the element has an error
* @attr error
* @readonly
*/
get error() { return this.#error }
set error(_) {}
static get properties() { return { error: { reflect: true } } }
onError(error) {
const oldError = this.error;
this.#error = error
this.requestUpdate('error', oldError)
}
We want to use the @deprecated
JSDoc tag to announce via the README and intellisense that a certain property should not be used, without releasing breaking changes.
It would be wonderful if this input
/**
* Size of the internal icon in px
* @attr icon-size
* @type {Number}
* @deprecated Since 3.0.0. Use `--icon-size` CSS property instead. Will be removed at next major version.
*/
@property({ type: Number, attribute: 'icon-size' }) iconSize = 20;
Which currently produces this README table:
## Properties
| Property| Attribute | Type| Default | Description |
|---------|-----------|-----|---------|-------------|
| `iconSize` | `icon-size` | `number` | 20| The internal icon's size. |
Would produce instead this expected table
## Properties
| Property| Attribute | Type| Default | Description |
|---------|-----------|-----|---------|-------------|
| `iconSize` | `icon-size` | `number` | 20| The internal icon's size.<br/>\
*DEPRECATED* Since 3.0.0. Use `--icon-size` CSS property instead. Will be removed at next major version. |
So I have this property specified in the mixin:
/**
* If true, the user cannot interact with this element.
*/
@property({ type: Boolean, reflect: true }) disabled = false;
The JSON output in the component extending that mixin:
{
"name": "disabled",
"type": "boolean"
},
This applies to all the properties inherited from the mixins, see example here:
https://cdn.vaadin.com/vaadin-details/2.0.0-alpha3/index.html
CSS shadow parts are available in Chrome 73+, Firefox 69 + (behind a flag) and WebKit nightly.
They are important part of the public CSS API, e.g. we are using part
in Vaadin components.
Let me suggest a syntax to document CSS Shadow Parts like this:
@csspart thumb - A part representing a switch thumb
@csspart track - A part representing a switch track
@csspart track-fill - An inner part (child of "track")
Parsing should be most likely the same as for Custom CSS properties.
If you think this feature would be a nice addition, I could try to submit a PR.
Given a plain JS lit-element like so:
/**
* @prop {boolean} myProperty - description for myProperty
*/
class MyElement extends LitElement {
static get properties() {
return {
myProperty: { type: Boolean }
}
}
constructor() {
super();
this.myProperty = false;
this.myBoundFunction = this.someFunction.bind(this);
}
someFunction() {
}
}
The (JSON) output contains both the myProperty
and myBoundFunction
properties. It seems that wca is analyzing not only the jsdoc but also the component's constructor. This seems to be causing two issues. The first is that the descriptions are not present in the output, and the second is that I wouldn't expect myBoundFunction
to be present in the output. I see that there is work to support @private
/@protected
, which may solve the latter issue.
This input
import { LitElement, html, query } from 'lit-element';
/** @element */
class LeakyDocs extends LitElement {
/**
* Value
* @attr
*/
@property({ type: String }) value;
/**
* Internal input
* @protected
*/
@query('#input') input
render() {
return html`<input id="input" .value="${this.value}"/>`;
}
}
produces this current output:
## Properties
| Property | Attribute | Type | Default | Description |
|----------|-----------|---------|---------|----------------|
| `input` | | `any` | | Internal input |
| `value` | `value` | `string`| | value |
WCA should produce instead an expected output without the protected property
## Properties
| Property | Attribute | Type | Default | Description |
|----------|-----------|---------|---------|----------------|
| `value` | `value` | `string`| | value |
For subclassing developers will need to see the protected fields they can access.
This might not be a bug, but rather unexpected behavior. So, I have this property:
get items(): Array<VaadinAccordionPanel> {
return super.items as Array<VaadinAccordionPanel>;
}
Which produces the following JSON:
{
"name": "items",
"type": "VaadinAccordionPanel[]"
},
IMO the expected behavior here would be to preserve Array<Type>
notation.
Currently I have to add a following JSDoc in order to enforce this notation:
/**
* @type {Array<VaadinAccordionPanel>}
*/
Hey @runem,
I use _propertyName
in my static get properties () {}
but it is not filtered out.
Seems like parseStaticProperties
does not use isPropNamePublic
.
Can I propose a PR maybe?
Consider:
// checkbox-mixin.d.ts
interface CheckboxMixin {
checked: boolean;
}
declare const CheckboxMixin: object;
export {CheckboxMixin};
// main.ts
import {html} from 'lit-element';
import {CheckboxMixin} from './checkbox_mixin';
interface TheCheckbox extends CheckboxMixin, HTMLElement {}
declare global {
interface HTMLElementTagNameMap {
'the-checkbox': TheCheckbox
}
}
console.log(html`
<the-checkbox .checked=${true}></the-checkbox>
`);
// .checked not found on the-checkbox!
It should be possible for the analyzer to pick up on properties used in the constructor of the custom element.
Properties prefixed with "#" or "_" are private and shouldn't be shown as properties on the element.
Example:
constructor() {
super();
this.title = 'You are awesome';
this.darkMode = false;
this.bar = { x: 0, y: 0, title: 'I am dot' };
this.formatter = null;
this._timeout = setTimeout(console.log, 1000);
}
Please correct me if I'm wrong but @readonly
does not show in JSON format.
I suppose this is related to webcomponents/custom-elements-manifest#5 (comment)
So maybe this is something that needs to be resolved (same as methods etc)?
This is a really impressive utility.
I've noticed that if I use the default formatter, I get a column for default values for properties.
But the default value is not provided if outputting to JSON. Would be great if JSON could also support default values.
Also, if the default value is a JS object, the keys get truncated to the first letter, e.g.:
newDataPointTestValue2 = {
label: "test",
index: 10,
valueFromEachDataset: []
};
produces {"l":"test","i":10,"v":[]}
The official jsdoc implementation requires the event to be defined with class and event type. Using the method described in the readme gives me a warning in IntelliJ editors: Unresolved variable or type 'event:input-switch-check-changed'
So instead of :
/* @fires input-switch-check-changed Fires when check property changes */
I need to write the following doc to get rid of the warning:
/* @fires InputSwitch#[CustomEvent]input-switch-check-changed Fires when check property changes */
I can understand you still want to support the original example because it's less verbose, but is it possible to also support the original jsdoc event definition?
Hi @runem! I just discovered wca and have been enjoying experimenting with it.
One thing I'd like to use wca for is to automatically generate JSON schemas for our components (the project is PatternFly Elements). My plan is to ask wca for JSON, then feed that JSON into a schema template, which will then output the final schema.json file.
Before going all-in on that though, I wanted to ask about this output from wca:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
WARNING: The json transformer hasn't been finished yet. You can expect large changes in this output.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
What sort of JSON changes do you have in mind for wca? For now, I pinned the version of wca so we won't be surprised by any JSON format changes, so we're all good there. I just wanted to get a sense of what you plan to change.
If I initialize a prop like:
/**
* This should be hidden from generated documentation
* @private
*/
@property({ attribute: false }) loading;
It will still be added to the table markdown.
Since it's a @private
it should be hidden from documentation
This is an intersection with the VSCode plugin, but from my logs this is the underlying component that has the issue.
First of all, thank you very much for your plugin, it's awesome!
I just noticed that a nullable type in a @prop
JSDoc tag seems to crash language server features. For example, after typing the below example @prop
line, other custom elements in my template no longer get hover-info, autocomplete etc.
Example:
import {html, css, LitElement} from 'lit-element';
/**
* @customElement error-dialog
* @prop {?Error} error
* @slot - Header Text
* @event errorack When the error acknowledgement button is pressed
*
* - some comments about css variables omitted -
*/
class ErrorDialog extends LitElement {
//... code
}
The @prop {?Error} error
bit seems to mess it up. Just for sureness' sake, I tried nullable notation on other types, eg. Number
and the same result occurred.
The logs I get when I enable verbose logging in the VSCode plugin:
�[31m ERROR: �[0m[ �[32m'Error [getQuickInfoAtPosition]: (TypeError: Cannot read property \'toLowerCase\' of undefined\n at parseJsDocTypeString (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:198:17)\n at parseJsDocTypeString (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:226:20)\n at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:817:43\n at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:750:20\n at Array.map (<anonymous>)\n at parseJsDocForNode (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:748:14)\n at parseDeclarationMembers$1 (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:811:26)\n at JsDocFlavor.parseDeclarationMembers (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:907:16)\n at _loop_1 (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1814:22)\n at executeFirstFlavor (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1826:27)\n at visitComponentDeclaration (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1766:25)\n at parseComponentDeclaration (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1680:5)\n at parseComponentDefinitions (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1936:31)\n at Object.analyzeComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:2066:32)\n at StoreUpdater.findComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:104:54)\n at StoreUpdater.findInvalidatedComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:70:22)\n at StoreUpdater.update (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:42:18)\n at TsLitPlugin.getQuickInfoAtPosition (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/ts-lit-plugin.js:106:27)\n at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/decorate-language-service.js:108:30\n at Object.nextLanguageService.(anonymous function) [as getQuickInfoAtPosition] (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/decorate-language-service.js:56:71)\n at IOSession.Session.getQuickInfoWorker (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131043:62)\n at Session.handlers.ts.createMapFromTemplate._a.(anonymous function) (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:130049:61)\n at /opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131665:88\n at IOSession.Session.executeWithRequestId (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131656:28)\n at IOSession.Session.executeCommand (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131665:33)\n at IOSession.Session.onMessage (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131687:35)\n at Interface.<anonymous> (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:132978:27)\n at Interface.emit (events.js:182:13)\n at Interface._onLine (readline.js:290:10)\n at Interface._normalWrite (readline.js:433:12)\n at Socket.ondata (readline.js:149:10)\n at Socket.emit (events.js:182:13)\n at addChunk (_stream_readable.js:283:12)\n at readableAddChunk (_stream_readable.js:264:11)\n at Socket.Readable.push (_stream_readable.js:219:10)\n at Pipe.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)) Cannot read property \'toLowerCase\' of undefined'�[39m,
TypeError: Cannot read property 'toLowerCase' of undefined
at parseJsDocTypeString (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:198:17)
at parseJsDocTypeString (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:226:20)
at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:817:43
at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:750:20
at Array.map (<anonymous>)
at parseJsDocForNode (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:748:14)
at parseDeclarationMembers$1 (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:811:26)
at JsDocFlavor.parseDeclarationMembers (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:907:16)
at _loop_1 (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1814:22)
at executeFirstFlavor (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1826:27)
at visitComponentDeclaration (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1766:25)
at parseComponentDeclaration (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1680:5)
at parseComponentDefinitions (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:1936:31)
at Object.analyzeComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/web-component-analyzer/lib/index.cjs.js:2066:32)
at StoreUpdater.findComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:104:54)
at StoreUpdater.findInvalidatedComponents (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:70:22)
at StoreUpdater.update (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/store-updater.js:42:18)
at TsLitPlugin.getQuickInfoAtPosition (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/language-service/ts-lit-plugin.js:106:27)
at /home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/decorate-language-service.js:108:30
at Object.nextLanguageService.(anonymous function) [as getQuickInfoAtPosition] (/home/nick/.vscode/extensions/runem.lit-plugin-1.0.2/node_modules/ts-lit-plugin/lib/decorate-language-service.js:56:71)
at IOSession.Session.getQuickInfoWorker (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131043:62)
at Session.handlers.ts.createMapFromTemplate._a.(anonymous function) (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:130049:61)
at /opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131665:88
at IOSession.Session.executeWithRequestId (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131656:28)
at IOSession.Session.executeCommand (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131665:33)
at IOSession.Session.onMessage (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:131687:35)
at Interface.<anonymous> (/opt/visual-studio-code/resources/app/extensions/node_modules/typescript/lib/tsserver.js:132978:27)
at Interface.emit (events.js:182:13)
at Interface._onLine (readline.js:290:10)
at Interface._normalWrite (readline.js:433:12)
at Socket.ondata (readline.js:149:10)
at Socket.emit (events.js:182:13)
at addChunk (_stream_readable.js:283:12)
at readableAddChunk (_stream_readable.js:264:11)
at Socket.Readable.push (_stream_readable.js:219:10)
at Pipe.onStreamRead [as onread] (internal/stream_base_commons.js:94:17) ]
Looking at that call stack, the second section looks like this:
// Match:
// {?number} (nullable)
// {!number} (not nullable)
// {...number} (array of)
var prefixMatch = str.match(/^(\?|!|(\.\.\.))(.+)$/);
if (prefixMatch != null) {
var modifier = prefixMatch[1];
var type = parseJsDocTypeString(prefixMatch[2]);
switch (modifier) {
which led me to infer that it might be the nullable notation, given the regex used there.
If I change the @prop
line to: @prop {Error | null}
(ultimately, the same thing), everything works again.
This is obviously not a showstopper, as like I mentioned you can just use the union type notation instead and work around it in the meantime.
Just wanted to let you know - thanks for your time!
We can include information currently not present in the vscode
format by adding it to the description
fields.
attributes
and tags
in the vscode
output format have description
fields that will show when the user hovers above them in the IDE.
Here we can include events, slots, attributes, properties, css vars like the following:
{
"name": "my-tag",
"description": "Here is the description\n\nEvents:\n * changed: Event description\n\nSlots:\n * right\n *left"
}
Here we can include type, associated property, default value.
{
"name": "text",
"description": "Here is the description\n\nType: string\nProperty: text\nDefault: 'default value'"
}
I will also have to look into if the vscode format supports using markdown in the description fields :-)
/**
* @fires load - This is the first line.
* This is the second line.
*/
Does not work, the description output for both json/markdown is empty for the event.
"events": [
{
"name": "load"
}
]
This works however:
/**
* @fires load - This is the first line. This is the second line.
*/
I want to introduce support for analyzing Polymer components. Specifically I want to implement searching for properties/attributes when extending the PolymerElement
.
A lot of this functionality is already implemented by the LitElement parsing flavor. However there are some differences between them, for example how attributes are mapped.
static get properties () {
return {
toggleIcon: {
type: String
},
pressed: {
type: Boolean,
value: false,
notify: true,
reflectToAttribute: true
}
};
}
Documentation from https://polymer-library.polymer-project.org/3.0/docs/first-element/step-3:
For this more complicated property, you supply a configuration object with several additional fields:
value: specifies the property's default value.
notify: tells Polymer to dispatch property change events when the property's value changes. This lets the change be observed by other nodes.
reflectToAttribute: tells Polymer to update the corresponding attribute when the property changes. This lets you style the element using an attribute selector, like icon-toggle[pressed].
Attribute names are converted to lowercase property names. For example, the attribute firstName maps to firstname.
Attribute names with dashes are converted to camelCase property names by capitalizing the character following each dash, then removing the dashes. For example, the attribute first-name maps to firstName.
Hello, we are using web-component-analyzer
to generate the documentation for our design system built with lit-element
. Right now we have a lot of type aliases to define that an attribute/property can have only a fixed set of values like this one:
type ButtonSize = "normal" | "small" | "large" | "none" | "extra-small";
The thing is that the output of web-component-analyzer
for this case is that the attribute/property is type ButtonSize
which is not really helpful for people who is reading the documentation online without reading the source.
I saw also in some repositories that maybe this is not the best behavior when the type has a lot of different options, maybe this can be enabled or disabled somehow? Any thoughts on that?
I think that maybe it would be really handy that, in the case we have an alias that is a union type composed only of strings literals, to print the value of those strings literals like: "normal" | "small" | "large" | "none" | "extra-small"
. This is exactly what is already happening when a union type is used but is not aliased, like in this case:
@property() size: "normal" | "small" | "large" | "none" | "extra-small" = "normal";
What do you think about this change? Do you think it makes sense?
I know that how the value is printed is handled by ts-simple-type but for this case I think it doesn't make sense to do it there.
I was already taking a look about how to implement it and I would be willing to provide a pull request 😄
Hey @runem,
This project awesome 😄
I'm using https://storybook.js.org/ to work on my components (and to showcase them). I would love to use your project to extract my JSDoc into some kind of object I could then format to HTML. This means I need direct access without the CLI part.
I tried to use your code without the CLI but I did not manage to go very far. Would you be interested to guide me so we could provide this kind of interaction?
The comment
is not added to the resulting markdown output.
☠ Failing usecase:
» file: src/first-compo.js
import { html, css, LitElement } from 'lit-element';
@customElement('first-compo')
export class FirstCompo extends LitElement {
static get properties() {
return {
/**
* This is my comment for the property `foo`
*/
foo: {
type: String,
},
};
}
constructor() {
this.foo = "init foo";
}
✓ Working usecase 1:
static get properties() {
return {
/**
* This is my comment for the property `foo`
*/
foo: {
type: String,
},
};
}
constructor() {
// initialization removed
}
Here the This is my comment for the property foo
will be used
✓ Working usecase 2:
static get properties() {
return {
/**
* This is my comment for the property `foo`
*/
foo: {
type: String,
},
};
}
constructor() {
/**
* My comment for foo in the constructor
*/
this.foo = "something"
}
Here, the My comment for foo in the constructor
will be used
It seems that if
this.foo = ...
) in the constructor of a property, the comment of the properties is added correctlyI would suggest to use the comment from the properties instead of nothing.
Including here so others can run but this is all I get as output:
command: wca analyze video-player.js --outFile custom-elements.js
output:
#
`video-player`
`A simple responsive video player with ridiculously powerful backing`
Input file of video-player.js
:
/**
* Copyright 2018 The Pennsylvania State University
* @license Apache-2.0, see License.md for full text.
*/
import { html } from "@polymer/polymer/polymer-element.js";
import { SimpleColors } from "@lrnwebcomponents/simple-colors/simple-colors.js";
import { A11yBehaviors } from "@lrnwebcomponents/a11y-behaviors/a11y-behaviors.js";
import "@polymer/polymer/lib/elements/dom-repeat.js";
import "@polymer/polymer/lib/elements/dom-if.js";
import { SchemaBehaviors } from "@lrnwebcomponents/schema-behaviors/schema-behaviors.js";
import { MediaBehaviorsVideo } from "@lrnwebcomponents/media-behaviors/media-behaviors.js";
import "@lrnwebcomponents/a11y-media-player/a11y-media-player.js";
/**
* `video-player`
* `A simple responsive video player with ridiculously powerful backing`
*
* @microcopy - language worth noting:
* - `video source` - url / link to the video file
* ```
<video-player
accent-color$="[[accentColor]]" // Optional accent color for controls,
// using the following materialize colors:
// red, pink, purple, deep-purple, indigo, blue,
// light blue, cyan, teal, green, light green, lime,
// yellow, amber, orange, deep-orange, and brown.
// Default is null.
dark$="[[dark]]" // Is the color scheme dark? Default is light.
dark-transcript$="[[darkTranscript]]" // Use dark theme on transcript? Default is false, even when player is dark.
disable-interactive$="[[disableInteractive]]" // Disable interactive cues?
height$="[[height]]" // The height of player
hide-timestamps$="[[hideTimestamps]]" // Hide cue timestamps?
lang$="[[lang]]" // The language of the media
media-title$="[[mediaTitle]]" // The title of the media
source$="[[source]]" // The source URL of the media
sticky-corner$="[[stickyCorner]]" // When user scrolls a playing video off-screen,
which corner will it stick to? Values are:
top-right (default), top-left, bottom-left, bottom-right,
and none (to turn sticky off)
thumbnail-src$="[[thumbnailSrc]]" // Optional thumbanil/cover image url
width$="[[width]]"> // The width of the media
<div slot="caption">Optional caption info.</div>
</video-player>```
*
* @customElement
* @polymer
* @demo demo/index.html
*/
class VideoPlayer extends MediaBehaviorsVideo(
A11yBehaviors(SchemaBehaviors(SimpleColors))
) {
// render function
static get template() {
return html`
<style>
:host {
display: block;
margin: 0 0 15px;
}
.video-caption {
font-style: italic;
margin: 0;
padding: 8px;
@apply --video-player-caption-theme;
}
</style>
<style include="simple-colors-shared-styles"></style>
<div style$="[[playerStyle]]">
<template is="dom-if" if="[[isA11yMedia]]" restamp>
<a11y-media-player
accent-color$="[[accentColor]]"
audio-only$="[[audioOnly]]"
dark$="[[dark]]"
dark-transcript$="[[darkTranscript]]"
disable-interactive$="[[disableInteractive]]"
hide-timestamps$="[[hideTimestamps]]"
hide-transcript$="[[hideTiranscript]]"
lang$="[[lang]]"
media-type$="[[sourceType]]"
preload$="[[preload]]"
media-title$="[[mediaTitle]]"
sources$="[[sourceData]]"
stand-alone$="[[__standAlone]]"
sticky-corner$="[[stickyCorner]]"
thumbnail-src$="[[thumbnailSrc]]"
tracks$="[[trackData]]"
crossorigin$="[[crossorigin]]"
youtube-id$="[[youtubeId]]"
>
<template
id="sources"
is="dom-repeat"
items="[[sourceData]]"
as="sd"
restamp
>
<source src$="[[sd.src]]" type$="[[sd.type]]" />
</template>
<template
id="tracks"
is="dom-repeat"
items="[[trackData]]"
as="track"
restamp
>
<track
src$="[[track.src]]"
kind$="[[track.kind]]"
label$="[[track.label]]"
srclang$="[[track.lang]]"
/>
</template>
<slot name="caption"></slot>
</a11y-media-player>
</template>
<template is="dom-if" if="[[!isA11yMedia]]">
<template is="dom-if" if="[[sandboxed]]">
<div class="responsive-video-container" lang$="[[lang]]">
<webview
resource$="[[schemaResourceID]]-video"
src$="[[sourceData.0.src]]"
width$="[[width]]"
height$="[[height]]"
frameborder="0"
></webview>
</div>
</template>
<template is="dom-if" if="[[!sandboxed]]">
<template is="dom-if" if="[[iframed]]">
<div class="responsive-video-container" lang$="[[lang]]">
<iframe
resource$="[[schemaResourceID]]-video"
src$="[[sourceData.0.src]]"
width$="[[width]]"
height$="[[height]]"
frameborder="0"
webkitallowfullscreen=""
mozallowfullscreen=""
allowfullscreen=""
></iframe>
</div>
</template>
</template>
<div id="videocaption" class$="video-caption">
<p>
[[mediaTitle]]
<span class="media-type print-only">(embedded media)</span>
</p>
<slot name="caption"></slot>
</div>
</template>
</div>
`;
}
// haxProperty definition
static get haxProperties() {
return {
canScale: true,
canPosition: true,
canEditSource: false,
gizmo: {
title: "Video player",
description:
"This can present video in a highly accessible manner regardless of source.",
icon: "av:play-circle-filled",
color: "red",
groups: ["Video", "Media"],
handles: [
{
type: "video",
source: "source",
title: "caption",
caption: "caption",
description: "caption",
color: "primaryColor"
}
],
meta: {
author: "LRNWebComponents"
}
},
settings: {
quick: [
{
property: "accentColor",
title: "Accent color",
description: "Select the accent color for the player.",
inputMethod: "colorpicker",
icon: "editor:format-color-fill"
},
{
attribute: "dark",
title: "Dark theme",
description: "Enable dark theme for the player.",
inputMethod: "boolean",
icon: "invert-colors"
}
],
configure: [
{
property: "source",
title: "Source",
description: "The URL for this video.",
inputMethod: "haxupload",
icon: "link",
required: true,
validationType: "url"
},
{
property: "track",
title: "Closed captions",
description: "The URL for the captions file.",
inputMethod: "textfield",
icon: "link",
required: true,
validationType: "url"
},
{
property: "thumbnailSrc",
title: "Thumbnail image",
description: "Optional. The URL for a thumbnail/poster image.",
inputMethod: "textfield",
icon: "link",
required: true,
validationType: "url"
},
{
property: "mediaTitle",
title: "Title",
description: "Simple title for under video",
inputMethod: "textfield",
icon: "av:video-label",
required: false,
validationType: "text"
},
{
property: "accentColor",
title: "Accent color",
description: "Select the accent color for the player.",
inputMethod: "colorpicker",
icon: "editor:format-color-fill"
},
{
attribute: "dark",
title: "Dark theme",
description: "Enable dark theme for the player.",
inputMethod: "boolean",
icon: "invert-colors"
}
],
advanced: [
{
property: "darkTranscript",
title: "Dark theme for transcript",
description: "Enable dark theme for the transcript.",
inputMethod: "boolean"
},
{
property: "hideTimestamps",
title: "Hide timestamps",
description: "Hide the time stamps on the transcript.",
inputMethod: "boolean"
},
{
property: "preload",
title: "Preload source(s).",
description:
"How the sources should be preloaded, i.e. auto, metadata (default), or none.",
inputMethod: "select",
options: {
preload: "Preload all media",
metadata: "Preload media metadata only",
none: "Don't preload anything"
}
},
{
property: "stickyCorner",
title: "Sticky Corner",
description:
"Set the corner where a video plays when scrolled out of range, or choose none to disable sticky video.",
inputMethod: "select",
options: {
none: "none",
"top-left": "top-left",
"top-right": "top-right",
"bottom-left": "bottom-left",
"bottom-right": "bottom-right"
}
},
{
property: "sources",
title: "Other sources",
description: "List of other sources",
inputMethod: "array",
properties: [
{
property: "src",
title: "Source",
description: "The URL for this video.",
inputMethod: "textfield"
},
{
property: "type",
title: "Type",
description: "Media type data",
inputMethod: "select",
options: {
"audio/aac": "acc audio",
"audio/flac": "flac audio",
"audio/mp3": "mp3 audio",
"video/mp4": "mp4 video",
"video/mov": "mov video",
"audio/ogg": "ogg audio",
"video/ogg": "ogg video",
"audio/wav": "wav audio",
"audio/webm": "webm audio",
"video/webm": "webm video"
}
}
]
},
{
property: "tracks",
title: "Track list",
description: "Tracks of different languages of closed captions",
inputMethod: "array",
properties: [
{
property: "kind",
title: "Kind",
description: "Kind of track",
inputMethod: "select",
options: {
subtitles:
"subtitles" /*,
Future Features
'description': 'description',
'thumbnails': 'thumbnails',
'interactive': 'interactive',
'annotation': 'annotation'*/
}
},
{
property: "label",
title: "Label",
description:
'The human-readable name for this track, eg. "English Subtitles"',
inputMethod: "textfield"
},
{
property: "src",
title: "Source",
description: "Source of the track",
inputMethod: "textfield"
},
{
property: "srclang",
title:
'Two letter, language code, eg. \'en\' for English, "de" for German, "es" for Spanish, etc.',
description: "Label",
inputMethod: "textfield"
}
]
}
]
}
};
}
// properties available to the custom element for data binding
static get properties() {
let props = {
/**
* Is the media an audio file only?
*/
audioOnly: {
type: Boolean,
value: false
},
/**
* Optional accent color for controls,
* using the following materialize "colors":
* red, pink, purple, deep-purple, indigo, blue,
* light blue, cyan, teal, green, light green, lime,
* yellow, amber, orange, deep-orange, and brown.
* Default is null.
*/
accentColor: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Cross origin flag for transcripts to load
*/
crossorigin: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Enables darker player.
*/
dark: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* Use dark theme on transcript? Default is false, even when player is dark.
*/
darkTranscript: {
type: Boolean,
value: false
},
/**
* disable interactive mode that makes the transcript clickable
*/
disableInteractive: {
type: Boolean,
value: false
},
/**
* The height of the media player for non-a11y-media.
*/
height: {
type: String,
value: null
},
/**
* show cue's start and end time
*/
hideTimestamps: {
type: Boolean,
value: false
},
/**
* hide the transcript by default
*/
hideTranscript: {
type: Boolean,
value: false
},
/**
* Computed if this should be in an iframe or not.
*/
iframed: {
type: Boolean,
computed: "_computeIframed(sourceData, sandboxed)"
},
/**
* Computed if this should be in a11y-media-player.
*/
isA11yMedia: {
type: Boolean,
computed: "_computeA11yMedia(sourceType, sandboxed)"
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
isYoutube: {
type: Boolean,
computed: "_computeYoutube(sourceType)"
},
/**
* The language of the media
*/
lang: {
type: String,
value: "en"
},
/**
* Simple caption for the video
*/
mediaTitle: {
type: String
},
/**
* What to preload for a11y-media-player: auto, metadata (default), or none.
*/
preload: {
type: String,
value: "metadata"
},
/* *
* Responsive video, calculated from not-responsive.
* /
"responsive": {
"type": Boolean,
"reflectToAttribute": true,
"value": true,
},*/
/**
* Compute if this is a sandboxed system or not
*/
sandboxed: {
type: Boolean,
computed: "_computeSandboxed(sourceData)"
},
/**
* Source of the video
*/
source: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Source of the video
*/
sources: {
type: Array,
value: []
},
/**
* List of source objects
*/
sourceData: {
type: Array,
computed: "_getSourceData(source,sources,trackData)"
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
sourceType: {
type: String,
computed: "_computeSourceType(sourceData)"
},
/**
* When playing but scrolled off screen, to which corner does it "stick":
* top-left, top-right, bottom-left, bottom-right, or none?
* Default is "top-right". "None" disables stickiness.
*/
stickyCorner: {
type: String,
value: "top-right",
reflectToAttribute: true
},
/**
* The url for a single subtitle track
*/
track: {
type: String,
value: null
},
/**
* Array of text tracks
* [{
* "src": "path/to/track.vtt",
* "label": "English",
* "srclang": "en",
* "kind": "subtitles",
* }]
*/
tracks: {
type: Array,
value: []
},
/**
* Cleaned array of text tracks
* [{
* "src": "path/to/track.vtt",
* "label": "English",
* "srclang": "en",
* "kind": "subtitles",
* }]
*/
trackData: {
type: Array,
computed: "_getTrackData(track,tracks)"
},
/**
* Source of optional thumbnail image
*/
thumbnailSrc: {
type: String,
value: null,
reflectToAttribute: true
},
/* *
* Calculate vimeo color based on accent color.
* /
"vimeoColor": {
"type": String,
"computed": getVimeoColor(dark,accentColor),
},
*/
/**
* The width of the media player for non-a11y-media.
*/
width: {
type: String,
value: null
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
youtubeId: {
type: String,
computed: "_computeYoutubeId(source,sourceType)"
}
};
if (super.properties) {
props = Object.assign(props, super.properties);
}
return props;
}
/**
* Store the tag name to make it easier to obtain directly.
* @notice function name must be here for tooling to operate correctly
*/
static get tag() {
return "video-player";
}
/**
* life cycle, element is afixed to the DOM
*/
connectedCallback() {
super.connectedCallback();
}
/**
* Get Youtube ID
*/
_computeYoutubeId(source, sourceType) {
if (source !== undefined && sourceType === "youtube") {
return this._computeSRC(source).replace(
/(https?:\/\/)?(www.)?youtube(-nocookie)?.com\/embed\//,
""
);
}
return false;
}
/**
* Determine if it is youtube
*/
_computeYoutube(sourceType) {
return sourceType === "youtube";
}
/**
* Determine if it is compatible with a11y-media-player
*/
_computeA11yMedia(sourceType, sandboxed) {
if (!sandboxed && (sourceType == "youtube" || sourceType == "local")) {
return true;
}
return false;
}
/**
* Compute iframed status
*/
_computeIframed(sourceData, sandboxed) {
// make sure we take into account sandboxing as well
// so that we can manage the state effectively
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
window.MediaBehaviors.Video._sourceIsIframe(sourceData[0].src) &&
!sandboxed
) {
return true;
}
return false;
}
/**
* Gets cleaned track list
*/
_getTrackData(track, tracks) {
let temp =
typeof tracks === "string" ? JSON.parse(tracks).slice() : tracks.slice();
if (track !== undefined && track !== null && track !== "")
temp.push({
src: track,
srclang: this.lang,
label: this.lang === "en" ? "English" : this.lang,
kind: "subtitles"
});
return temp;
}
/**
* Gets source and added to sources list
*/
_getSourceData(source, sources, trackData) {
if (typeof sources === "string") sources = JSON.parse(sources);
let root = this,
temp = sources.slice();
for (let i = 0; i < temp.length; i++) {
temp[i].type =
temp[i].type !== undefined && temp[i].type !== null
? temp[i].type
: this._computeMediaType(temp[i].src);
temp[i].src = this._computeSRC(temp[i].src);
}
if (source !== null) {
let src = this._computeSRC(source);
this.sourceType = this._computeSourceType(src);
if (this.sourceType !== "youtube") {
temp.unshift({ src: src, type: this._computeMediaType(src) });
}
}
this.__standAlone =
trackData === undefined || trackData === null || trackData.length < 1;
return temp;
}
/**
* Compute media type based on source, i.e. 'audio/wav' for '.wav'
*/
_computeMediaType(source) {
let root = this,
audio = ["aac", "flac", "mp3", "oga", "wav"],
video = ["mov", "mp4", "ogv", "webm"],
type = "",
findType = function(text, data) {
for (let i = 0; i < data.length; i++) {
if (
type === "" &&
source !== undefined &&
source !== null &&
source.toLowerCase().indexOf("." + data[i]) > -1
) {
if (text === "audio") root.audioOnly = true;
type = text + "/" + data[i];
}
}
};
findType("audio", audio);
findType("video", video);
return type;
}
/**
* Compute sandboxed status
*/
_computeSandboxed(sourceData) {
// we have something that would require an iframe
// see if we have a local system that would want to sandbox instead
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
window.MediaBehaviors.Video._sourceIsIframe(sourceData[0].src)
) {
// fake the creation of a webview element to see if it's valid
// or not.
let test = document.createElement("webview");
// if this function exists it means that our deploy target
// is in a sandboxed environment and is not able to run iframe
// content with any real stability. This is beyond edge case but
// as this is an incredibly useful tag we want to make sure it
// can mutate to work in chromium and android environments
// which support such sandboxing
if (typeof test.reload === "function") {
return true;
}
}
return false;
}
/**
* Compute video type based on source
*/
_computeSourceType(sourceData) {
let root = this;
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
typeof sourceData[0].src !== typeof undefined
) {
return window.MediaBehaviors.Video.getVideoType(sourceData[0].src);
} else {
return null;
}
}
/**
* Compute src from type / source combo.
* Type is set by source so this ensures a waterfall
* of valid values.
*/
_computeSRC(source) {
if (source !== null && typeof source !== undefined) {
let type =
this.sourceType !== undefined
? this.sourceType
: window.MediaBehaviors.Video.getVideoType(source);
// ensure that this is a valid url / cleaned up a bit
source = window.MediaBehaviors.Video.cleanVideoSource(source, type);
if (type == "vimeo") {
if (this.vimeoTitle) {
source += "?title=1";
} else {
source += "?title=0";
}
if (this.vimeoByline) {
source += "&byline=1";
} else {
source += "&byline=0";
}
if (this.vimeoPortrait) {
source += "&portrait=1";
} else {
source += "&portrait=0";
}
if (typeof this.videoColor !== typeof undefined) {
source += "&color=" + this.videoColor;
}
} else if (type == "dailymotion") {
source += "&ui-start-screen-info=false";
source += "&ui-logo=false";
source += "&sharing-enable=false";
source += "&endscreen-enable=false";
if (typeof this.videoColor !== typeof undefined) {
source += "&ui-highlight=" + this.videoColor;
}
}
}
return source;
}
/**
* postProcesshaxNodeToContent - clean up so we don't have empty array data
*/
postProcesshaxNodeToContent(content) {
content = content.replace(' sources="[]",', "");
content = content.replace(' tracks="[]",', "");
return content;
}
}
window.customElements.define(VideoPlayer.tag, VideoPlayer);
export { VideoPlayer };
Right now web-component-analyzer
doesn't support mixins. This issue will track the implementation of that feature.
const SomeMixin = (Base) => {
return class Mixin extends Base {
static get observedAttributes() {
return ["c", "d"];
}
}
}
class SomeElement extends SomeMixin(HTMLElement) {
static get observedAttributes() {
return ["a", "b", ...super.observedAttributes];
}
}
Analyzing the above should find "a"/"b" attributes (which it does right now) and it should find "c"/"d" attributes (which it doesn't do right now).
type Constructor<T = {}> = new (...args: any[]) => T;
const SomeMixin = <C extends Constructor<HTMLElement>>(Base: C) => {
class Mixin extends Base {
@property({ type: String }) mixinProperty: string;
}
return Mixin;
}
@customElement("some-element")
class SomeElement extends SomeMixin(LitElement) {
@property({ type: String }) elementProperty: string;
}
Analyzing the above should find "elementProperty" (which it does right now) and it should find "mixinProperty" (which it doesn't do right now).
There is a small room for improvement after #111 has been merged: as of now, custom CSS properties are in the reverse order (compared to how they are annotated in JSDOc).
I would recommend to either keep the original order or sort alphabetically, as the issue says.
Hi !
Thanks for your tool, it's really appreciated !
I've seen a regression in version 0.1.12 : cssprop are no longer generated in json output. It's working in markdown output thought.
It was working in 0.1.11.
Thanks
This file
import { svg, LitElement } from 'lit-element';
/** @typedef {import('lit-element').SVGTemplateResult} SVGTemplateResult */
/**
* @element
*/
class FailedParse extends LitElement {
/**
* @param {string} name
* @return {SVGTemplateResult}
*/
svgTemplate(name) {
return svg`<svg id="${name}"></svg>`;
}
}
Fails to parse.
Removing the imported typedef
allows it to parse properly.
Hey ;-)
I would really like to document all my LitElement components without using any TypeScript. I was wondering how I could document the type of an object property?
Let's consider this example:
export class TestComponent extends LitElement {
static get properties () {
return {
foo: { type: Object, attribute: false },
};
}
}
A scan gives me this:
{
"version": 2,
"tags": [
{
"name": "test-component",
"properties": [
{
"name": "foo",
"type": "{}"
}
]
}
]
}
I would like wca to output "type": "MyType"
and also expose the definition of MyType
so it could end up in the documentation.
I tried many combinations with @type
, @prop
and @typedef
in the JSDoc on top of the class and on top of the foo: { ...}
but nothing really successful.
"type": "MyType"
?MyType
?I have come across a scenario where I would like to emit an event from a host element to a child element in reaction to something which is intended to be internal only and not display in the documented output.
Perhaps using the @private
tag as referenced in #106
@customElement('testing')
export default class Testing extends LitElement {
render() {
return html`<child-element></child-element>`
}
firstUpdated() {
let child = document.querySelector('child-element')
setTimeout(() => {
child.dispatchEvent(new CustomEvent('ping'))
}, 1000)
}
}
omit eventName ping
from the documentation.
I have been spending the last couple of weeks on refactoring/improving many parts of the analyzer. Most notable, I have been improving performance, jsdoc-support and member-merging. Some of the requirements meant that I had to change core parts of the analyzer, eg. to support lazy-evaluation for performance improvements. Soon I'll be finishing up the work, push it to the branch refactor
, and afterwards I will begin preparing a version 1.0.0
release.
The following is a description of what have changed. Feel free to give your feedback, thoughts or additional needs. It would be especially interesting to get some feedback on my examples of how component members are merged now.
In the past, the entire component, with all of its features, was analyzed when running the analyzer. This means, that an IDE-plugin discovering custom element tag names (eg. on startup) had to analyze all elements, which could result in performance issues. The analyzed components would often be of no use, because only a subset of components are actually used in a given source file. In order to address performance issues, I'm implementing the following 4 things in WCA:
In order to use WCA in the browser such as https://runem.github.io/web-component-analyzer, I will make some changes to what this library exports. Specifically, I will:
In the past, WCA was able to emit both documentation and diagnostics. In order to clean up things, I'm removing the functionality to emit diagnostics. Instead, it's now possible for WCA to add flavor-specific metadata to analyzed features that can be read programmatically. For example, the content of LitElement's @property
decorator will now be available to read on the result. Thus lit-analyzer
will now be able to do more things with LitElement-specific information, eg. to emit diagnostics.
I will look into supporting @extends
, @mixes
, @example
, @method / @function
, @typedef
. I haven't started working on them, but the architecture supports it now :-) Existing supported jsdoc tags will work better because of improved merging logic.
I have been making a lot of changes to the way components are analyzed. It's now possible for flavors to hook into much more than before in a much cleaner flow. This image should give an overview of the architecture:
Each flavor finds features on components. Features can be "properties", "attributes", "slot", eg. Multiple features can be emitted per property (eg. if you have both a constructor assignment and a class field that reference the same property). I'm making a lot of changes to how these features are merged.
Here are some of the highlights:
required
) prefers the value of the first non-undefined value found@type
JSDoc. In JS-file the @type
JSDoc is preferred over the type checkerHere are some examples so you can see how merging will work. They are a bit contrived, but that's on purpose because I want to show how overlapping fields and comments are merged.
/**
* @element
* @prop {"red"|"green"} foo - Test 1
*/
class MyElement extends HTMLElement {
/**
* Test 2
*/
foo = "green";
constructor () {
super();
/**
* Test 3
*/
this.foo = "red";
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | "red" | "green" | Test 1 | "green" (*) |
/**
* @element
* @prop {MyType} foo
*/
class MyElement extends LitElement {
static get properties () {
return {
/**
* Test 1
* @required
*/
foo: { type: Object, attribute: false },
};
}
constructor () {
super();
/**
* Test 2
* @type {"blue"|"red"}
* @protected
*/
this.foo = {}
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | MyType | Test 1 | {} | yes | protected |
/**
* @element
* @prop {("blue" | "red)} foo - Test 1
*/
class MyElement extends LitElement {
static get properties () {
return {
/**
* @private
*/
foo: { type: String, attribute: "bar" }
}
}
static get observedAttributes () {
return ["foo"];
}
constructor () {
super();
/**
* Test 3
* @type {string}
* @required
*/
this.foo = "bar 2";
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | bar | string | Test 1 | "bar 2" | yes | private |
/**
* @element
* @prop {"red"|"green"} foo - Test 1
*/
class MyElement extends HTMLElement {
/**
* Test 2
*/
foo: MyType;
constructor () {
super();
/**
* This is ignored, because we are a TS-file
* @required
*/
this.foo = "red";
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | MyType | Test 1 |
/**
* @element
*/
class MyElement extends LitElement {
foo: "red" | "green" = "green";
static get properties () {
return {
/**
* @private
*/
foo: { type: String, attribute: "bar" }
}
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | bar | "red" | "green" | Test 2 | "green" | private |
/**
* @element
* @prop {string} foo - Test 1
*/
class MyElement extends HTMLElement {
/**
* Test 2
*/
foo: number = 123;
static get observedAttributes () {
return ["foo"];
}
}
Result
Prop Name | Attr Name | Type | Description | Default | Required | Visibility |
---|---|---|---|---|---|---|
foo | foo | number | Test 1 | 123 |
The analyzer currently seems to be looking for the static tag name as supplied in the customElements.define("what-ever", WhatEver);
If this is generated off the class itself or defined elsewhere this can cause issues causing an empty output for the tag name. Possible solutions:
Referencing a commit on this to see what generated blank though I'm sure almost all our elements will since we use this convention.
I was going to take a stab at adding a CLI test, but I ran into issues running the existing test suite. Error log below. Is there anything other than npm install; npm test
that I need to run to set up the test suite?
> [email protected] test /home/user/projects/wca > ava
✔ components › custom-element-test.ts › Custom Element: Attributes
Uncaught exception in test/snapshots/stencil-progress-ring.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: /progress-ring-component/dist/types/components.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/stencil-progress-ring.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)Uncaught exception in test/snapshots/polymer-app-layout.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: @polymer/app-layout/app-drawer/app-drawer.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/polymer-app-layout.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)Uncaught exception in test/snapshots/lit-element-mwc.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: @material/mwc-button/mwc-button.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/lit-element-mwc.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)Uncaught exception in test/snapshots/stencil-ionic.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: @material/mwc-button/mwc-button.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/stencil-ionic.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)Uncaught exception in test/snapshots/polymer-paper-button.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: @polymer/paper-button/paper-button.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/polymer-paper-button.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)Uncaught exception in test/snapshots/vanilla-masonry-layout.ts
/home/user/projects/wca/test/helpers/source-file-test.ts:30
29: console.log(_compiledResult.program.getSourceFiles().map(sf => sf.fi…
30: throw new Error(Couldn't find source file match: ${fileName}
);
31: } else if (sourceFiles.length > 1) {Error: Couldn't find source file match: @appnest/masonry-layout/masonry-layout.d.ts
getSourceFile (test/helpers/source-file-test.ts:30:9)
testResult (test/helpers/source-file-test.ts:40:8)
Object.testResultSnapshot (test/helpers/source-file-test.ts:49:2)
Object. (test/snapshots/vanilla-masonry-layout.ts:4:1)
Module.m._compile (node_modules/ts-node/src/index.ts:439:23)
Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:442:12)✖ test/snapshots/stencil-progress-ring.ts exited with a non-zero exit code: 1
✖ test/snapshots/vanilla-masonry-layout.ts exited with a non-zero exit code: 1
✖ test/snapshots/polymer-app-layout.ts exited with a non-zero exit code: 1
✖ test/snapshots/polymer-paper-button.ts exited with a non-zero exit code: 1
✖ test/snapshots/lit-element-mwc.ts exited with a non-zero exit code: 1
✖ test/snapshots/stencil-ionic.ts exited with a non-zero exit code: 11 test passed
6 uncaught exceptions
Analyzing this code:
type Constructor<T> = {
new (...args: any[]): T
};
type ConstructorOne = Constructor<{someMethod(): void;}>;
type ConstructorTwo = Constructor<{someMethod(): void;}>;
type Combined = ConstructorOne&ConstructorTwo;
declare const Combined: Combined;
declare class ExtendsCombined extends Combined {}
export class C extends HTMLElement {
field!: ExtendsCombined;
}
customElements.define('s-e', C);
results in the following crash:
TypeError: Cannot read property 'kind' of undefined
at Object.isBindingElement (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/typescript/lib/typescript.js:13739:21)
at getCombinedFlags (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/typescript/lib/typescript.js:13081:16)
at Object.getCombinedModifierFlags (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/typescript/lib/typescript.js:13098:16)
at getModifiersFromDeclaration (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:230:37)
at /Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:499:28
at Array.map (<anonymous>)
at toSimpleTypeInternal (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:497:63)
at toSimpleTypeInternalCaching (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:291:33)
at toSimpleType (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:257:12)
at Object.isAssignableToSimpleTypeKind (/Users/rictic/open/fuck/repro-mixin-simple-type/node_modules/ts-simple-type/lib/index.cjs.js:654:45)
This is a minimal reduction of a bug from a LitElement class that had a field that was an array of PolymerElements, whose type is defined by application of a number of class mixins.
Hey, I'm not sure what I'm doing wrong but I tried to reduce the test case:
import { html, LitElement } from 'lit-element';
export class TestComponent extends LitElement {
render () {
const someBoolean = (42 > 2);
return html`<div>${someBoolean ? 'yes' : 'no'}</div>`;
}
}
window.customElements.define('test-component', TestComponent);
When I run wca
, the JSON output picks up the internal variable someBoolean
.
{
"version": 2,
"tags": [
{
"name": "test-component",
"properties": [
{
"name": "someBoolean",
"type": "\"yes\" | \"no\""
}
]
}
]
}
Thanks for creating this great project! I am able to use it for some of our api doc extraction already.
I have hit two problems in my Typescript/LitElement project:
@fires change
, those seem to be ignoredI am seeing errors like this:
Fatal error: Invalid regular expression: /^(\s*?\{(?<type>.*)\})?(((?<name1>.+)(\s\-\s)(?<comment1>.+))|(\s?\-\s)(?<comment2>.+)|(?<name2>.*?))$/: Invalid group
SyntaxError: Invalid regular expression: /^(\s*?\{(?<type>.*)\})?(((?<name1>.+)(\s\-\s)(?<comment1>.+))|(\s?\-\s)(?<comment2>.+)|(?<name2>.*?))$/: Invalid group
at parseJsDocTag (/Users/mitaylor/Scratch/wc-analyze-test/node_modules/web-component-analyzer/lib/index.cjs.js:241:17)
I have attached a simple test project that demonstrates this (wc-analyze-test.zip).
cd wc-analyze-test
npm install
npm run analyze
If I delete some of the tags from component.ts
, it runs, but the @fires
is ignored. Edit the JSDoc comment to look like:
/**
* Here is a description of my web component.
*
* @element my-element
*
* @fires change - This jsdoc tag makes it possible to document events.
* @fires submit
*/
Then try npm analyze
again. It should run, but you won't see any mention of the change event in the output.
Like what you did in the playground?
I would like to integrate in-browser parsing into webcomponents.dev
Thanks!
Consider a project like:
// base.ts
export interface Checked {
checked: boolean;
}
// main.ts
import {Checked} from './base';
interface CheckableElement extends HTMLElement, Checked {}
declare global {
interface HTMLElementTagNameMap {
'checkable-element': CheckableElement;
}
}
web-component-analyzer appears not to know that <checkable-element
> has a .checked
property. The issue seems to be specific to interfaces that extend imported interfaces. Specifically, I checked these cases:
Standalone repro at https://github.com/rictic/repro-wca-import-interface-issue
export class TextField extends HTMLElement {
size?: 'type'|'large';
}
The type is reported as "type": "\"type\" | \"large\""
, but should be "type": "\"type\" | \"large\" | undefined"
.
It would be nice if wca had an option to concatenate its output to an existing README.md, while bumping up it's header levels.
Input:
/**
* @element 'super-man'
* @attr {String} [weakness="kryptonite"] - supe's ultimate weakness
*
* ### Usage
* ```html
* <super-man weakness="lois-lane"></super-man>
* ```
*/
class SuperMan extends HTMLElement {}
/**
* @element 'spider-man'
* @attr {'classic'|'alien'} [suit="classic"] - type of spidey suit
*
* ### Usage
* ```html
* <spider-man suit="classic"></spider-man>
* ```
*/
class SpiderMan extends HTMLElement {}
Existing README.md
# Super Elements
Super elements for super developers.
Here's a bunch of emoji
[![made with open-wc](https://img.shields.io/badge/made%20with-open--wc-%23217ff9)](https://open-wc.org)
[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](collection/super-elements/smart-elements-community)
Output:
# Super Elements
Super elements for super developers.
Here's a bunch of emoji
[![made with open-wc](https://img.shields.io/badge/made%20with-open--wc-%23217ff9)](https://open-wc.org)
[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](collection/super-elements/smart-elements-community)
## super-man
### Usage
```html
<super-man weakness="lois-lane"></super-man>
```
| Property | Attribute | Modifiers | Type | Default | Description |
|------------|------------|-----------|----------|--------------|--------------------------|
| `weakness` | `weakness` | | `String` | 'kryptonite' | supe's ultimate weakness |
## spider-man
### Usage
```html
<spider-man suit="classic"></spider-man>
```
| Property | Attribute | Modifiers | Type | Default | Description |
|----------|-----------|-----------|------------------|-----------|---------------------|
| `suit` | `suit` | | `classic|alien` | 'classic' | type of spidey suit |
This could be accomplished by the user with a little bash scripting and some regex, but it would be nice to have some --merge
flag that defaults to README.md
Hello,
Thank you for your effort on building this useful tool 👍
I have a question regarding name of the interface that is used during the analyze of ts files:
Would be feasible to customize the name of an interface for analysis. For instance, HTMLElementTagNameMap
to IntrinsicElements
?
What do you think?
The docs seem to indicate that methods should be included in the output, but I don't see them.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.