Skip to content

Commit

Permalink
breaking: required props should be inferred, remove @required tag (#91
Browse files Browse the repository at this point in the history
)

* fix: uninitialized props should be required

Fixes #22

* Run "yarn test:snapshot"

* chore: prop is required if kind is `let` and init is `null`

* breaking: remove `@required` tag

* Run "yarn test:snapshot"

* Run "yarn test:integration"
  • Loading branch information
metonym authored May 21, 2022
1 parent 5a8fac2 commit 06997cf
Show file tree
Hide file tree
Showing 18 changed files with 90 additions and 86 deletions.
31 changes: 0 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export default class Button extends SvelteComponentTyped<
- [API Reference](#api-reference)
- [@type](#type)
- [@typedef](#typedef)
- [@required](#required)
- [@slot](#slot)
- [@event](#event)
- [@restProps](#restprops)
Expand Down Expand Up @@ -351,36 +350,6 @@ export let author = {};
export let authors = [];
```

### `@required`

By default, all props are typed as optional.

Use the `@required` tag to denote a component prop as required.

Example:

```js
/**
* This prop is required
* @required
*/
export let isRequired = true;
```

TypeScript output:

```ts
export interface ComponentProps {
/**
* This prop is required
* @default true
*/
isRequired: boolean;
}
```

Because `@required` is non-standard JSDoc tag, it is omitted from the prop comment in the TypeScript definitions.

### `@slot`

Use the `@slot` tag for typing component slots. Note that `@slot` is a non-standard JSDoc tag.
Expand Down
4 changes: 2 additions & 2 deletions integration/carbon/COMPONENT_API.json
Original file line number Diff line number Diff line change
Expand Up @@ -2596,12 +2596,12 @@
{
"name": "itemToString",
"kind": "let",
"description": "Override the display of a dropdown item",
"description": "Override the display of a dropdown item\n@required ",
"type": "(item: DropdownItem) => string",
"value": "(item) => item.text || item.id",
"isFunction": true,
"isFunctionDeclaration": false,
"isRequired": true,
"isRequired": false,
"constant": false,
"reactive": false
},
Expand Down
44 changes: 22 additions & 22 deletions integration/carbon/COMPONENT_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -969,28 +969,28 @@ export interface DropdownItem {

### Props

| Prop name | Required | Kind | Reactive | Type | Default value | Description |
| :-------------- | :------- | :--------------- | :------- | ------------------------------------------- | ----------------------------------------------------- | --------------------------------------------- |
| ref | No | <code>let</code> | Yes | <code>null &#124; HTMLButtonElement</code> | <code>null</code> | Obtain a reference to the button HTML element |
| inline | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to use the inline variant |
| open | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the dropdown |
| selectedIndex | No | <code>let</code> | Yes | <code>number</code> | <code>-1</code> | Specify the selected item index |
| items | No | <code>let</code> | No | <code>DropdownItem[]</code> | <code>[]</code> | Set the dropdown items |
| itemToString | Yes | <code>let</code> | No | <code>(item: DropdownItem) => string</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a dropdown item |
| type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of dropdown |
| size | No | <code>let</code> | No | <code>"sm" &#124; "lg" &#124; "xl"</code> | <code>undefined</code> | Specify the size of the dropdown field |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown |
| titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| label | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify the list box label |
| translateWithId | No | <code>let</code> | No | <code>(id: any) => string</code> | <code>undefined</code> | Override the default translation ids |
| id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component |
| name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the list box |
| Prop name | Required | Kind | Reactive | Type | Default value | Description |
| :-------------- | :------- | :--------------- | :------- | ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| ref | No | <code>let</code> | Yes | <code>null &#124; HTMLButtonElement</code> | <code>null</code> | Obtain a reference to the button HTML element |
| inline | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to use the inline variant |
| open | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the dropdown |
| selectedIndex | No | <code>let</code> | Yes | <code>number</code> | <code>-1</code> | Specify the selected item index |
| items | No | <code>let</code> | No | <code>DropdownItem[]</code> | <code>[]</code> | Set the dropdown items |
| itemToString | No | <code>let</code> | No | <code>(item: DropdownItem) => string</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a dropdown item<br />@required |
| type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of dropdown |
| size | No | <code>let</code> | No | <code>"sm" &#124; "lg" &#124; "xl"</code> | <code>undefined</code> | Specify the size of the dropdown field |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown |
| titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| label | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify the list box label |
| translateWithId | No | <code>let</code> | No | <code>(id: any) => string</code> | <code>undefined</code> | Override the default translation ids |
| id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component |
| name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the list box |

### Slots

Expand Down
3 changes: 2 additions & 1 deletion integration/carbon/types/Dropdown/Dropdown.svelte.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export interface DropdownProps

/**
* Override the display of a dropdown item
* @required
* @default (item) => item.text || item.id
*/
itemToString: (item: DropdownItem) => string;
itemToString?: (item: DropdownItem) => string;

/**
* Specify the selected item index
Expand Down
18 changes: 8 additions & 10 deletions src/ComponentParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export default class ComponentParser {
let description: undefined | string = undefined;
let isFunction = false;
let isFunctionDeclaration = false;
let isRequired = false;
let isRequired = kind === "let" && init == null;

if (init != null) {
if (
Expand Down Expand Up @@ -505,21 +505,19 @@ export default class ComponentParser {
if (tag?.tag === "type") type = this.aliasType(tag.type);
description = ComponentParser.assignValue(comment[0]?.description?.trim());

const additional_tags = comment[0]?.tags.filter(
(tag) => !["type", "extends", "restProps", "slot", "event", "typedef"].includes(tag.tag)
) ?? [];
const additional_tags =
comment[0]?.tags.filter(
(tag) => !["type", "extends", "restProps", "slot", "event", "typedef"].includes(tag.tag)
) ?? [];

if (additional_tags.length > 0 && description === undefined) {
description = "";
}

additional_tags.forEach((tag) => {
isRequired = tag.tag === "required";
if (!isRequired) {
description += `${description ? "\n" : ""}@${tag.tag} ${tag.name}${
tag.description ? ` ${tag.description}` : ""
}`;
}
description += `${description ? "\n" : ""}@${tag.tag} ${tag.name}${
tag.description ? ` ${tag.description}` : ""
}`;
});
}

Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/bind-this-multiple/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ export interface InputProps {
/**
* @default undefined
*/
ref?: null | HTMLButtonElement | HTMLHeadingElement;
ref: null | HTMLButtonElement | HTMLHeadingElement;

/**
* @default undefined
*/
ref2?: null | HTMLDivElement;
ref2: null | HTMLDivElement;

/**
* @default false
Expand Down
4 changes: 2 additions & 2 deletions tests/snapshots/bind-this-multiple/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "null | HTMLButtonElement | HTMLHeadingElement",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": true
},
Expand All @@ -16,7 +16,7 @@
"type": "null | HTMLDivElement",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": true
},
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/bind-this/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface InputProps {
/**
* @default undefined
*/
ref?: null | HTMLButtonElement;
ref: null | HTMLButtonElement;
}

export default class Input extends SvelteComponentTyped<InputProps, {}, { default: {} }> {}
2 changes: 1 addition & 1 deletion tests/snapshots/bind-this/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "null | HTMLButtonElement",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": true
}
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/infer-basic/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface InputProps {
/**
* @default undefined
*/
name?: undefined;
name: undefined;

/**
* @default "" + Math.random().toString(36)
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/infer-basic/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"kind": "let",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": false
},
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/infer-with-types/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface InputProps {
/**
* @default undefined
*/
name?: string;
name: string;

/**
* @default "" + Math.random().toString(36)
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/infer-with-types/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"type": "string",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": false
},
Expand Down
7 changes: 6 additions & 1 deletion tests/snapshots/required/input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
* @type {boolean | string}
*/
export let prop1 = true;
export let prop2;
/** @type {boolean} */
export let prop3;
</script>

<slot {prop} {prop1} />
<slot {prop} {prop1} {prop2} {prop3} />
18 changes: 15 additions & 3 deletions tests/snapshots/required/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,31 @@ import type { SvelteComponentTyped } from "svelte";

export interface InputProps {
/**
* @required
* @default true
*/
prop: boolean;
prop?: boolean;

/**
* This is a comment.
* @required
* @default true
*/
prop1: boolean | string;
prop1?: boolean | string;

/**
* @default undefined
*/
prop2: undefined;

/**
* @default undefined
*/
prop3: boolean;
}

export default class Input extends SvelteComponentTyped<
InputProps,
{},
{ default: { prop: boolean; prop1: boolean | string } }
{ default: { prop: boolean; prop1: boolean | string; prop2: any; prop3: boolean } }
> {}
27 changes: 23 additions & 4 deletions tests/snapshots/required/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,42 @@
{
"name": "prop",
"kind": "let",
"description": "",
"description": "@required ",
"type": "boolean",
"value": "true",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": true,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "prop1",
"kind": "let",
"description": "This is a comment.",
"description": "This is a comment.\n@required ",
"type": "boolean | string",
"value": "true",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "prop2",
"kind": "let",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": true,
"constant": false,
"reactive": false
},
{
"name": "prop3",
"kind": "let",
"type": "boolean",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": true,
"constant": false,
"reactive": false
Expand All @@ -30,7 +49,7 @@
{
"name": "__default__",
"default": true,
"slot_props": "{ prop: boolean, prop1: boolean | string }"
"slot_props": "{ prop: boolean, prop1: boolean | string, prop2: any, prop3: boolean }"
}
],
"events": [],
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/typed-props/output.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface InputProps {
* prop1 description 2
* @default undefined
*/
prop1?: string;
prop1: string;

/**
* prop2 description 1
Expand Down
2 changes: 1 addition & 1 deletion tests/snapshots/typed-props/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "string",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"isRequired": true,
"constant": false,
"reactive": false
},
Expand Down

0 comments on commit 06997cf

Please sign in to comment.