diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8b6a94..06fd712 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,8 @@ jobs: node-version: ${{ matrix.config.node }} - name: Install Dependencies run: npm ci + - name: Check API Docs + run: npm run doc && git diff --exit-code - name: Check Format run: npm run format && git diff --exit-code - name: Lint diff --git a/.vscodeignore b/.vscodeignore index e507f03..37ff9f3 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -8,10 +8,10 @@ src/** .prettierignore CHANGELOG.md webpack.config.js -vsc-extension-quickstart.md **/tsconfig.json **/.eslintrc.json **/*.map **/*.ts release.config.js .nyc_output/** +docs/** diff --git a/README.md b/README.md index 7b27fbd..0395c0a 100644 --- a/README.md +++ b/README.md @@ -10,31 +10,7 @@ This VS Code extension does not provide any functionality but a bridge between t ## API -### Variables - -| Name | Description | Type | Note | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | :---------: | -| `openedSketches` | All opened sketch folders in the window. | `SketchFolder[]` | ⚠️ `@alpha` | -| `currentSketch` | The currently active sketch (folder) or `undefined`. The current sketch is the one that currently has focus or most recently had focus. The current sketch is in the opened sketches. | `SketchFolder \| undefined` | ⚠️ `@alpha` | -| `config` | The currently configured Arduino CLI configuration. | `CliConfig` | ⚠️ `@alpha` | - -### Events - -| Name | Description | Type | Note | -| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | :---------: | -| `onDidChangeCurrentSketch` | An event that is emitted when the current sketch has changed. _Note_ that the event also fires when the active editor changes to `undefined`. | `Event<{ currentSketch: SketchFolder \| undefined }>` | ⚠️ `@alpha` | -| `onDidChangeSketchFolders` | An event that is emitted when sketch folders are added or removed. | `Event` | ⚠️ `@alpha` | -| `onDidChangeSketch` | An event that is emitted when the selected board, port, etc., has changed in the sketch folder. | `Event>` | ⚠️ `@alpha` | -| `onDidChangeConfig` | An event that is emitter when the sketchbook (`directories.data`) or the data directory (`directories.data`) path has changed. | `Event>` | ⚠️ `@alpha` | - -### `SketchFolder` - -| Name | Description | Type | Note | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | :---------: | -| `sketchPath` | Absolute filesystem path of the sketch folder. | `string` | ⚠️ `@alpha` | -| `compileSummary` | The summary of the latest sketch compilation. When the `sketchPath` is available but the sketch has not been verified (compiled), the compile summary can be `undefined`. | `CompileSummary` | ⚠️ `@alpha` | -| `board` | The currently selected board associated with the sketch. If the `board` is undefined, no board is selected. If the `board` is a `BoardIdentifier`, it could be a recognized board on a detected port, but the board's platform could be absent. If platform is installed, the `board` is the lightweight representation of the board's detail. This information is [provided by the Arduino CLI](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.BoardDetailsResponse) for the currently selected board in the sketch folder. | `string` | ⚠️ `@alpha` | -| `port` | The currently selected port in the sketch folder. | [`Port`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#port) | ⚠️ `@alpha` | +[See](https://github.com/dankeboy36/vscode-arduino-api/blob/main/docs/README.md) the full API on GitHub. ## How to Use @@ -70,7 +46,7 @@ If you want to use the Arduino APIs, you have to do the followings: context.subscriptions.push( vscode.commands.registerCommand('myExtension.showSketchPath', () => { vscode.window.showInformationMessage( - `Sketch path: ${arduinoContext.sketchPath}` + `Sketch path: ${context.sketchPath}` ); }) ); diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e2ac661 --- /dev/null +++ b/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..011f826 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,140 @@ +# vscode-arduino-api + +## Table of contents + +### Classes + +- [Disposable](classes/Disposable.md) + +### Interfaces + +- [ArduinoContext](interfaces/ArduinoContext.md) +- [ArduinoState](interfaces/ArduinoState.md) +- [BoardDetails](interfaces/BoardDetails.md) +- [ChangeEvent](interfaces/ChangeEvent.md) +- [CliConfig](interfaces/CliConfig.md) +- [CompileSummary](interfaces/CompileSummary.md) +- [ConfigOption](interfaces/ConfigOption.md) +- [ConfigValue](interfaces/ConfigValue.md) +- [Event](interfaces/Event.md) +- [Port](interfaces/Port.md) +- [Programmer](interfaces/Programmer.md) +- [SketchFolder](interfaces/SketchFolder.md) +- [SketchFoldersChangeEvent](interfaces/SketchFoldersChangeEvent.md) + +### Type Aliases + +- [BoardIdentifier](README.md#boardidentifier) +- [BuildProperties](README.md#buildproperties) +- [Tool](README.md#tool) +- [Version](README.md#version) + +### Variables + +- [ConfigOption](README.md#configoption) +- [ConfigValue](README.md#configvalue) +- [Port](README.md#port) +- [Programmer](README.md#programmer) + +## Type Aliases + +### BoardIdentifier + +Ƭ **BoardIdentifier**: `Nullable`\<`ApiBoard`, `"fqbn"`\> + +Lightweight information to identify a board: + +- The board's `name` is to provide a fallback for the UI. Preferably do not use this property for any sophisticated logic and board comparison. It must never participate in the board's identification. +- The FQBN might contain boards config options if selected from the discovered ports (see [arduino/arduino-ide#1588](https://github.com/arduino/arduino-ide/issues/1588)). + +--- + +### BuildProperties + +Ƭ **BuildProperties**: `Readonly`\<`Record`\<`string`, `string`\>\> + +Build properties used for compiling. The board-specific properties are retrieved from `board.txt` and `platform.txt`. For example, if the `board.txt` contains the `build.tarch=xtensa` entry for the `esp32:esp32:esp32` board, the record includes the `"build.tarch": "xtensa"` property. + +--- + +### Tool + +Ƭ **Tool**: `Readonly`\<`Pick`\<`ToolsDependencies`, `"name"` \| `"version"` \| `"packager"`\>\> + +Required Tool dependencies of a board. See [`ToolsDependencies`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.ToolsDependencies) for the CLI API. + +--- + +### Version + +Ƭ **Version**: `string` + +Supposed to be a [SemVer](https://semver.org/) as a `string` but it's not enforced by Arduino. You might need to coerce the SemVer string. + +## Variables + +### ConfigOption + +• **ConfigOption**: `Object` + +#### Type declaration + +| Name | Type | +| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `create` | (`base?`: \{ `option?`: `string` ; `optionLabel?`: `string` ; `values?`: \{ `selected?`: `boolean` ; `value?`: `string` ; `valueLabel?`: `string` }[] }) => [`ConfigOption`](interfaces/ConfigOption.md) | +| `decode` | (`input`: `Uint8Array` \| `Reader`, `length?`: `number`) => [`ConfigOption`](interfaces/ConfigOption.md) | +| `encode` | (`message`: [`ConfigOption`](interfaces/ConfigOption.md), `writer?`: `Writer`) => `Writer` | +| `fromJSON` | (`object`: `any`) => [`ConfigOption`](interfaces/ConfigOption.md) | +| `fromPartial` | (`object`: \{ `option?`: `string` ; `optionLabel?`: `string` ; `values?`: \{ `selected?`: `boolean` ; `value?`: `string` ; `valueLabel?`: `string` }[] }) => [`ConfigOption`](interfaces/ConfigOption.md) | +| `toJSON` | (`message`: [`ConfigOption`](interfaces/ConfigOption.md)) => `unknown` | + +--- + +### ConfigValue + +• **ConfigValue**: `Object` + +#### Type declaration + +| Name | Type | +| :------------ | :----------------------------------------------------------------------------------------------------------------------------------- | +| `create` | (`base?`: \{ `selected?`: `boolean` ; `value?`: `string` ; `valueLabel?`: `string` }) => [`ConfigValue`](interfaces/ConfigValue.md) | +| `decode` | (`input`: `Uint8Array` \| `Reader`, `length?`: `number`) => [`ConfigValue`](interfaces/ConfigValue.md) | +| `encode` | (`message`: [`ConfigValue`](interfaces/ConfigValue.md), `writer?`: `Writer`) => `Writer` | +| `fromJSON` | (`object`: `any`) => [`ConfigValue`](interfaces/ConfigValue.md) | +| `fromPartial` | (`object`: \{ `selected?`: `boolean` ; `value?`: `string` ; `valueLabel?`: `string` }) => [`ConfigValue`](interfaces/ConfigValue.md) | +| `toJSON` | (`message`: [`ConfigValue`](interfaces/ConfigValue.md)) => `unknown` | + +--- + +### Port + +• **Port**: `Object` + +#### Type declaration + +| Name | Type | +| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `create` | (`base?`: \{ `address?`: `string` ; `hardwareId?`: `string` ; `label?`: `string` ; `properties?`: {} ; `protocol?`: `string` ; `protocolLabel?`: `string` }) => [`Port`](interfaces/Port.md) | +| `decode` | (`input`: `Uint8Array` \| `Reader`, `length?`: `number`) => [`Port`](interfaces/Port.md) | +| `encode` | (`message`: [`Port`](interfaces/Port.md), `writer?`: `Writer`) => `Writer` | +| `fromJSON` | (`object`: `any`) => [`Port`](interfaces/Port.md) | +| `fromPartial` | (`object`: \{ `address?`: `string` ; `hardwareId?`: `string` ; `label?`: `string` ; `properties?`: {} ; `protocol?`: `string` ; `protocolLabel?`: `string` }) => [`Port`](interfaces/Port.md) | +| `toJSON` | (`message`: [`Port`](interfaces/Port.md)) => `unknown` | + +--- + +### Programmer + +• **Programmer**: `Object` + +#### Type declaration + +| Name | Type | +| :------------ | :----------------------------------------------------------------------------------------------------------------------- | +| `create` | (`base?`: \{ `id?`: `string` ; `name?`: `string` ; `platform?`: `string` }) => [`Programmer`](interfaces/Programmer.md) | +| `decode` | (`input`: `Uint8Array` \| `Reader`, `length?`: `number`) => [`Programmer`](interfaces/Programmer.md) | +| `encode` | (`message`: [`Programmer`](interfaces/Programmer.md), `writer?`: `Writer`) => `Writer` | +| `fromJSON` | (`object`: `any`) => [`Programmer`](interfaces/Programmer.md) | +| `fromPartial` | (`object`: \{ `id?`: `string` ; `name?`: `string` ; `platform?`: `string` }) => [`Programmer`](interfaces/Programmer.md) | +| `toJSON` | (`message`: [`Programmer`](interfaces/Programmer.md)) => `unknown` | diff --git a/docs/classes/Disposable.md b/docs/classes/Disposable.md new file mode 100644 index 0000000..0e166ad --- /dev/null +++ b/docs/classes/Disposable.md @@ -0,0 +1,70 @@ +# Class: Disposable + +Represents a type which can release resources, such +as event listening or a timer. + +## Table of contents + +### Constructors + +- [constructor](Disposable.md#constructor) + +### Methods + +- [dispose](Disposable.md#dispose) +- [from](Disposable.md#from) + +## Constructors + +### constructor + +• **new Disposable**(`callOnDispose`): [`Disposable`](Disposable.md) + +Creates a new disposable that calls the provided function +on dispose. + +_Note_ that an asynchronous function is not awaited. + +#### Parameters + +| Name | Type | Description | +| :-------------- | :---------- | :-------------------------------- | +| `callOnDispose` | () => `any` | Function that disposes something. | + +#### Returns + +[`Disposable`](Disposable.md) + +## Methods + +### dispose + +▸ **dispose**(): `any` + +Dispose this object. + +#### Returns + +`any` + +--- + +### from + +▸ **from**(`...disposableLikes`): [`Disposable`](Disposable.md) + +Combine many disposable-likes into one. You can use this method when having objects with +a dispose function which aren't instances of `Disposable`. + +#### Parameters + +| Name | Type | Description | +| :------------------- | :---------------------------- | :--------------------------------------------------------------------------------------------------------------- | +| `...disposableLikes` | \{ `dispose`: () => `any` }[] | Objects that have at least a `dispose`-function member. Note that asynchronous dispose-functions aren't awaited. | + +#### Returns + +[`Disposable`](Disposable.md) + +Returns a new disposable which, upon dispose, will +dispose all provided disposables. diff --git a/docs/interfaces/ArduinoContext.md b/docs/interfaces/ArduinoContext.md new file mode 100644 index 0000000..f10dddc --- /dev/null +++ b/docs/interfaces/ArduinoContext.md @@ -0,0 +1,228 @@ +# Interface: ArduinoContext + +Provides access to the current state of the Arduino IDE such as the sketch path, the currently selected board, and port, and etc. + +## Hierarchy + +- [`ArduinoState`](ArduinoState.md) + + ↳ **`ArduinoContext`** + +## Table of contents + +### Properties + +- [boardDetails](ArduinoContext.md#boarddetails) +- [compileSummary](ArduinoContext.md#compilesummary) +- [config](ArduinoContext.md#config) +- [currentSketch](ArduinoContext.md#currentsketch) +- [dataDirPath](ArduinoContext.md#datadirpath) +- [fqbn](ArduinoContext.md#fqbn) +- [onDidChangeConfig](ArduinoContext.md#ondidchangeconfig) +- [onDidChangeCurrentSketch](ArduinoContext.md#ondidchangecurrentsketch) +- [onDidChangeSketch](ArduinoContext.md#ondidchangesketch) +- [onDidChangeSketchFolders](ArduinoContext.md#ondidchangesketchfolders) +- [openedSketches](ArduinoContext.md#openedsketches) +- [port](ArduinoContext.md#port) +- [sketchPath](ArduinoContext.md#sketchpath) +- [userDirPath](ArduinoContext.md#userdirpath) + +### Methods + +- [onDidChange](ArduinoContext.md#ondidchange) + +## Properties + +### boardDetails + +• `Readonly` **boardDetails**: `undefined` \| [`BoardDetails`](BoardDetails.md) + +Lightweight representation of the board's detail. This information is [provided by the Arduino CLI](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.BoardDetailsResponse) for the currently selected board. It can be `undefined` if the `fqbn` is defined but the platform is not installed. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.boardDetails` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[boardDetails](ArduinoState.md#boarddetails) + +--- + +### compileSummary + +• `Readonly` **compileSummary**: `undefined` \| [`CompileSummary`](CompileSummary.md) + +The summary of the latest sketch compilation. When the `sketchPath` is available but the sketch has not been verified (compiled), the `buildPath` can be `undefined`. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.compileSummary` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[compileSummary](ArduinoState.md#compilesummary) + +--- + +### config + +• `Readonly` **config**: [`CliConfig`](CliConfig.md) + +The currently configured Arduino CLI configuration. + +--- + +### currentSketch + +• `Readonly` **currentSketch**: `undefined` \| [`SketchFolder`](SketchFolder.md) + +The currently active sketch (folder) or `undefined`. The current sketch is the one that currently has focus or most recently had focus. +The current sketch is in the [opened sketches](ArduinoContext.md#openedsketches). + +--- + +### dataDirPath + +• `Readonly` **dataDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.data`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. + +**`Deprecated`** + +Use `arduinoContext?.config?.dataDirPath` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[dataDirPath](ArduinoState.md#datadirpath) + +--- + +### fqbn + +• `Readonly` **fqbn**: `undefined` \| `string` + +The Fully Qualified Board Name (FQBN) of the currently selected board in the Arduino IDE. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.board?.fqbn` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[fqbn](ArduinoState.md#fqbn) + +--- + +### onDidChangeConfig + +• `Readonly` **onDidChangeConfig**: [`Event`](Event.md)\<[`ChangeEvent`](ChangeEvent.md)\<[`CliConfig`](CliConfig.md)\>\> + +An event that is emitter when the [sketchbook](CliConfig.md#userdirpath) (`directories.data`) or the [data directory](CliConfig.md#datadirpath) (`directories.data`) path has changed. + +--- + +### onDidChangeCurrentSketch + +• `Readonly` **onDidChangeCurrentSketch**: [`Event`](Event.md)\<`undefined` \| [`SketchFolder`](SketchFolder.md)\> + +An [Event](Event.md) that is emitted when the [current sketch](ArduinoContext.md#currentsketch) has changed. +_Note_ that the event also fires when the active editor changes to `undefined`. + +--- + +### onDidChangeSketch + +• `Readonly` **onDidChangeSketch**: [`Event`](Event.md)\<[`ChangeEvent`](ChangeEvent.md)\<[`SketchFolder`](SketchFolder.md)\>\> + +An event that is emitted when the selected [board](SketchFolder.md#board), [port](SketchFolder.md#port), etc., has changed in the [sketch folder](SketchFolder.md). + +--- + +### onDidChangeSketchFolders + +• `Readonly` **onDidChangeSketchFolders**: [`Event`](Event.md)\<[`SketchFoldersChangeEvent`](SketchFoldersChangeEvent.md)\> + +An event that is emitted when sketch folders are added or removed. + +--- + +### openedSketches + +• `Readonly` **openedSketches**: readonly [`SketchFolder`](SketchFolder.md)[] + +All opened sketch folders in the window. + +--- + +### port + +• `Readonly` **port**: `undefined` \| [`Port`](Port.md) + +The currently selected port in the Arduino IDE. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.port` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[port](ArduinoState.md#port) + +--- + +### sketchPath + +• `Readonly` **sketchPath**: `undefined` \| `string` + +Absolute filesystem path of the sketch folder. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.sketchPath` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[sketchPath](ArduinoState.md#sketchpath) + +--- + +### userDirPath + +• `Readonly` **userDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.user`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. This is the sketchbook path. + +**`Deprecated`** + +Use `arduinoContext?.config?.userDirPath` instead. + +#### Inherited from + +[ArduinoState](ArduinoState.md).[userDirPath](ArduinoState.md#userdirpath) + +## Methods + +### onDidChange + +▸ **onDidChange**\<`T`\>(`property`): [`Event`](Event.md)\<[`ArduinoState`](ArduinoState.md)[`T`]\> + +#### Type parameters + +| Name | Type | +| :--- | :---------------------------------------------- | +| `T` | extends keyof [`ArduinoState`](ArduinoState.md) | + +#### Parameters + +| Name | Type | +| :--------- | :--- | +| `property` | `T` | + +#### Returns + +[`Event`](Event.md)\<[`ArduinoState`](ArduinoState.md)[`T`]\> + +**`Deprecated`** + +Use `onDidChangeSketch` and `onDidChangeConfig` instead. diff --git a/docs/interfaces/ArduinoState.md b/docs/interfaces/ArduinoState.md new file mode 100644 index 0000000..3415758 --- /dev/null +++ b/docs/interfaces/ArduinoState.md @@ -0,0 +1,105 @@ +# Interface: ArduinoState + +The current state of the Arduino IDE. + +## Hierarchy + +- **`ArduinoState`** + + ↳ [`ArduinoContext`](ArduinoContext.md) + +## Table of contents + +### Properties + +- [boardDetails](ArduinoState.md#boarddetails) +- [compileSummary](ArduinoState.md#compilesummary) +- [dataDirPath](ArduinoState.md#datadirpath) +- [fqbn](ArduinoState.md#fqbn) +- [port](ArduinoState.md#port) +- [sketchPath](ArduinoState.md#sketchpath) +- [userDirPath](ArduinoState.md#userdirpath) + +## Properties + +### boardDetails + +• `Readonly` **boardDetails**: `undefined` \| [`BoardDetails`](BoardDetails.md) + +Lightweight representation of the board's detail. This information is [provided by the Arduino CLI](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.BoardDetailsResponse) for the currently selected board. It can be `undefined` if the `fqbn` is defined but the platform is not installed. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.boardDetails` instead. + +--- + +### compileSummary + +• `Readonly` **compileSummary**: `undefined` \| [`CompileSummary`](CompileSummary.md) + +The summary of the latest sketch compilation. When the `sketchPath` is available but the sketch has not been verified (compiled), the `buildPath` can be `undefined`. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.compileSummary` instead. + +--- + +### dataDirPath + +• `Readonly` **dataDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.data`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. + +**`Deprecated`** + +Use `arduinoContext?.config?.dataDirPath` instead. + +--- + +### fqbn + +• `Readonly` **fqbn**: `undefined` \| `string` + +The Fully Qualified Board Name (FQBN) of the currently selected board in the Arduino IDE. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.board?.fqbn` instead. + +--- + +### port + +• `Readonly` **port**: `undefined` \| [`Port`](Port.md) + +The currently selected port in the Arduino IDE. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.port` instead. + +--- + +### sketchPath + +• `Readonly` **sketchPath**: `undefined` \| `string` + +Absolute filesystem path of the sketch folder. + +**`Deprecated`** + +Use `arduinoContext?.currentSketch?.sketchPath` instead. + +--- + +### userDirPath + +• `Readonly` **userDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.user`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. This is the sketchbook path. + +**`Deprecated`** + +Use `arduinoContext?.config?.userDirPath` instead. diff --git a/docs/interfaces/BoardDetails.md b/docs/interfaces/BoardDetails.md new file mode 100644 index 0000000..39495f3 --- /dev/null +++ b/docs/interfaces/BoardDetails.md @@ -0,0 +1,93 @@ +# Interface: BoardDetails + +The lightweight representation of all details of a particular board. See [`BoardDetailsResponse`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.BoardDetailsResponse) for the CLI API. + +## Hierarchy + +- `Readonly`\<`Pick`\<`BoardDetailsResponse`, `"fqbn"` \| `"name"` \| `"configOptions"` \| `"programmers"` \| `"defaultProgrammerId"`\>\> + + ↳ **`BoardDetails`** + +## Table of contents + +### Properties + +- [buildProperties](BoardDetails.md#buildproperties) +- [configOptions](BoardDetails.md#configoptions) +- [defaultProgrammerId](BoardDetails.md#defaultprogrammerid) +- [fqbn](BoardDetails.md#fqbn) +- [name](BoardDetails.md#name) +- [programmers](BoardDetails.md#programmers) +- [toolsDependencies](BoardDetails.md#toolsdependencies) + +## Properties + +### buildProperties + +• `Readonly` **buildProperties**: `Readonly`\<`Record`\<`string`, `string`\>\> + +--- + +### configOptions + +• `Readonly` **configOptions**: [`ConfigOption`](ConfigOption.md)[] + +The board's custom configuration options. + +#### Inherited from + +Readonly.configOptions + +--- + +### defaultProgrammerId + +• `Readonly` **defaultProgrammerId**: `string` + +Default programmer for the board + +#### Inherited from + +Readonly.defaultProgrammerId + +--- + +### fqbn + +• `Readonly` **fqbn**: `string` + +The fully qualified board name of the board. + +#### Inherited from + +Readonly.fqbn + +--- + +### name + +• `Readonly` **name**: `string` + +Name used to identify the board to humans (e.g., Arduino Uno). + +#### Inherited from + +Readonly.name + +--- + +### programmers + +• `Readonly` **programmers**: [`Programmer`](Programmer.md)[] + +List of programmers supported by the board + +#### Inherited from + +Readonly.programmers + +--- + +### toolsDependencies + +• `Readonly` **toolsDependencies**: `Readonly`\<`Pick`\<`ToolsDependencies`, `"name"` \| `"version"` \| `"packager"`\>\>[] diff --git a/docs/interfaces/ChangeEvent.md b/docs/interfaces/ChangeEvent.md new file mode 100644 index 0000000..610922a --- /dev/null +++ b/docs/interfaces/ChangeEvent.md @@ -0,0 +1,32 @@ +# Interface: ChangeEvent\ + +Describes a change event with the new state of the `object` and an array indicating which property has changed. + +## Type parameters + +| Name | +| :--- | +| `T` | + +## Table of contents + +### Properties + +- [changedProperties](ChangeEvent.md#changedproperties) +- [object](ChangeEvent.md#object) + +## Properties + +### changedProperties + +• `Readonly` **changedProperties**: readonly keyof `T`[] + +An array properties that have changed in the `object`. + +--- + +### object + +• `Readonly` **object**: `T` + +The new state of the object diff --git a/docs/interfaces/CliConfig.md b/docs/interfaces/CliConfig.md new file mode 100644 index 0000000..63d3cb9 --- /dev/null +++ b/docs/interfaces/CliConfig.md @@ -0,0 +1,26 @@ +# Interface: CliConfig + +Bare minimum representation of the Arduino CLI [configuration](https://arduino.github.io/arduino-cli/latest/configuration). + +## Table of contents + +### Properties + +- [dataDirPath](CliConfig.md#datadirpath) +- [userDirPath](CliConfig.md#userdirpath) + +## Properties + +### dataDirPath + +• `Readonly` **dataDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.data`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. + +--- + +### userDirPath + +• `Readonly` **userDirPath**: `undefined` \| `string` + +Filesystem path to the [`directories.user`](https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys) location. This is the sketchbook path. diff --git a/docs/interfaces/CompileSummary.md b/docs/interfaces/CompileSummary.md new file mode 100644 index 0000000..3c19e42 --- /dev/null +++ b/docs/interfaces/CompileSummary.md @@ -0,0 +1,86 @@ +# Interface: CompileSummary + +Summary of a sketch compilation. See [`CompileResponse`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#compileresponse) for the CLI API. + +## Hierarchy + +- `Readonly`\<`Pick`\<`CompileResponse`, `"buildPath"` \| `"usedLibraries"` \| `"executableSectionsSize"` \| `"boardPlatform"` \| `"buildPlatform"`\>\> + + ↳ **`CompileSummary`** + +## Table of contents + +### Properties + +- [boardPlatform](CompileSummary.md#boardplatform) +- [buildPath](CompileSummary.md#buildpath) +- [buildPlatform](CompileSummary.md#buildplatform) +- [buildProperties](CompileSummary.md#buildproperties) +- [executableSectionsSize](CompileSummary.md#executablesectionssize) +- [usedLibraries](CompileSummary.md#usedlibraries) + +## Properties + +### boardPlatform + +• `Readonly` **boardPlatform**: `undefined` \| `InstalledPlatformReference` + +The platform where the board is defined + +#### Inherited from + +Readonly.boardPlatform + +--- + +### buildPath + +• `Readonly` **buildPath**: `string` + +The compiler build path + +#### Inherited from + +Readonly.buildPath + +--- + +### buildPlatform + +• `Readonly` **buildPlatform**: `undefined` \| `InstalledPlatformReference` + +The platform used for the build (if referenced from the board platform) + +#### Inherited from + +Readonly.buildPlatform + +--- + +### buildProperties + +• `Readonly` **buildProperties**: `Readonly`\<`Record`\<`string`, `string`\>\> + +--- + +### executableSectionsSize + +• `Readonly` **executableSectionsSize**: `ExecutableSectionSize`[] + +The size of the executable split by sections + +#### Inherited from + +Readonly.executableSectionsSize + +--- + +### usedLibraries + +• `Readonly` **usedLibraries**: `Library`[] + +The libraries used in the build + +#### Inherited from + +Readonly.usedLibraries diff --git a/docs/interfaces/ConfigOption.md b/docs/interfaces/ConfigOption.md new file mode 100644 index 0000000..8673f6f --- /dev/null +++ b/docs/interfaces/ConfigOption.md @@ -0,0 +1,33 @@ +# Interface: ConfigOption + +## Table of contents + +### Properties + +- [option](ConfigOption.md#option) +- [optionLabel](ConfigOption.md#optionlabel) +- [values](ConfigOption.md#values) + +## Properties + +### option + +• **option**: `string` + +ID of the configuration option. For identifying the option to machines. + +--- + +### optionLabel + +• **optionLabel**: `string` + +Name of the configuration option for identifying the option to humans. + +--- + +### values + +• **values**: [`ConfigValue`](ConfigValue.md)[] + +Possible values of the configuration option. diff --git a/docs/interfaces/ConfigValue.md b/docs/interfaces/ConfigValue.md new file mode 100644 index 0000000..e9765b2 --- /dev/null +++ b/docs/interfaces/ConfigValue.md @@ -0,0 +1,33 @@ +# Interface: ConfigValue + +## Table of contents + +### Properties + +- [selected](ConfigValue.md#selected) +- [value](ConfigValue.md#value) +- [valueLabel](ConfigValue.md#valuelabel) + +## Properties + +### selected + +• **selected**: `boolean` + +Whether the configuration option is selected. + +--- + +### value + +• **value**: `string` + +The configuration option value. + +--- + +### valueLabel + +• **valueLabel**: `string` + +Label to identify the configuration option to humans. diff --git a/docs/interfaces/Event.md b/docs/interfaces/Event.md new file mode 100644 index 0000000..850d1fd --- /dev/null +++ b/docs/interfaces/Event.md @@ -0,0 +1,43 @@ +# Interface: Event\ + +Represents a typed event. + +A function that represents an event to which you subscribe by calling it with +a listener function as argument. + +**`Example`** + +```ts +item.onDidChange(function (event) { + console.log('Event happened: ' + event); +}); +``` + +## Type parameters + +| Name | +| :--- | +| `T` | + +## Callable + +### Event + +▸ **Event**(`listener`, `thisArgs?`, `disposables?`): [`Disposable`](../classes/Disposable.md) + +A function that represents an event to which you subscribe by calling it with +a listener function as argument. + +#### Parameters + +| Name | Type | Description | +| :------------- | :----------------------------------------- | :------------------------------------------------------------------------ | +| `listener` | (`e`: `T`) => `any` | The listener function will be called when the event happens. | +| `thisArgs?` | `any` | The `this`-argument which will be used when calling the event listener. | +| `disposables?` | [`Disposable`](../classes/Disposable.md)[] | An array to which a [Disposable](../classes/Disposable.md) will be added. | + +#### Returns + +[`Disposable`](../classes/Disposable.md) + +A disposable which unsubscribes the event listener. diff --git a/docs/interfaces/Port.md b/docs/interfaces/Port.md new file mode 100644 index 0000000..e72ef5e --- /dev/null +++ b/docs/interfaces/Port.md @@ -0,0 +1,66 @@ +# Interface: Port + +Port represents a board port that may be used to upload or to monitor a board + +## Table of contents + +### Properties + +- [address](Port.md#address) +- [hardwareId](Port.md#hardwareid) +- [label](Port.md#label) +- [properties](Port.md#properties) +- [protocol](Port.md#protocol) +- [protocolLabel](Port.md#protocollabel) + +## Properties + +### address + +• **address**: `string` + +Address of the port (e.g., `/dev/ttyACM0`). + +--- + +### hardwareId + +• **hardwareId**: `string` + +The hardware ID (serial number) of the board attached to the port + +--- + +### label + +• **label**: `string` + +The port label to show on the GUI (e.g. "ttyACM0") + +--- + +### properties + +• **properties**: `Object` + +A set of properties of the port + +#### Index signature + +▪ [key: `string`]: `string` + +--- + +### protocol + +• **protocol**: `string` + +Protocol of the port (e.g., `serial`, `network`, ...). + +--- + +### protocolLabel + +• **protocolLabel**: `string` + +A human friendly description of the protocol (e.g., "Serial Port (USB)"). diff --git a/docs/interfaces/Programmer.md b/docs/interfaces/Programmer.md new file mode 100644 index 0000000..b2cd867 --- /dev/null +++ b/docs/interfaces/Programmer.md @@ -0,0 +1,33 @@ +# Interface: Programmer + +## Table of contents + +### Properties + +- [id](Programmer.md#id) +- [name](Programmer.md#name) +- [platform](Programmer.md#platform) + +## Properties + +### id + +• **id**: `string` + +Programmer ID + +--- + +### name + +• **name**: `string` + +Programmer name + +--- + +### platform + +• **platform**: `string` + +Platform name diff --git a/docs/interfaces/SketchFolder.md b/docs/interfaces/SketchFolder.md new file mode 100644 index 0000000..8c45700 --- /dev/null +++ b/docs/interfaces/SketchFolder.md @@ -0,0 +1,66 @@ +# Interface: SketchFolder + +The current state in the sketch folder. For example, the FQBN of the selected board, the selected port, etc. + +## Table of contents + +### Properties + +- [board](SketchFolder.md#board) +- [compileSummary](SketchFolder.md#compilesummary) +- [configOptions](SketchFolder.md#configoptions) +- [port](SketchFolder.md#port) +- [selectedProgrammer](SketchFolder.md#selectedprogrammer) +- [sketchPath](SketchFolder.md#sketchpath) + +## Properties + +### board + +• `Readonly` **board**: `undefined` \| [`BoardIdentifier`](../README.md#boardidentifier) \| [`BoardDetails`](BoardDetails.md) + +The currently selected board associated with the sketch. If the `board` is undefined, no board is selected. +If the `board` is a `BoardIdentifier`, it could be a recognized board on a detected port, but the board's platform could be absent. +If platform is installed, the `board` is the lightweight representation of the board's detail. This information is +[provided by the Arduino CLI](https://arduino.github.io/arduino-cli/latest/rpc/commands/#cc.arduino.cli.commands.v1.BoardDetailsResponse) +for the currently selected board in the sketch folder. + +--- + +### compileSummary + +• `Readonly` **compileSummary**: `undefined` \| [`CompileSummary`](CompileSummary.md) + +The summary of the latest sketch compilation. When the `sketchPath` is available but the sketch has not been verified (compiled), the compile summary can be `undefined`. + +--- + +### configOptions + +• `Readonly` **configOptions**: `undefined` \| `string` + +The FQBN with all the custom board options (if any) for the sketch. `a:b:c:opt1=value_1,opt2=value_2` means `{ "opt1": "value_1", "opt2": "value_2" }` config options are configured. + +--- + +### port + +• `Readonly` **port**: `undefined` \| `Readonly`\<`Pick`\<[`Port`](Port.md), `"address"` \| `"protocol"`\>\> \| `Readonly`\<[`Port`](Port.md)\> + +The currently selected port in the sketch folder. + +--- + +### selectedProgrammer + +• `Readonly` **selectedProgrammer**: `undefined` \| `string` \| `Readonly`\<[`Programmer`](Programmer.md)\> + +The currently selected programmer. + +--- + +### sketchPath + +• `Readonly` **sketchPath**: `string` + +Absolute filesystem path of the sketch folder. diff --git a/docs/interfaces/SketchFoldersChangeEvent.md b/docs/interfaces/SketchFoldersChangeEvent.md new file mode 100644 index 0000000..82093fb --- /dev/null +++ b/docs/interfaces/SketchFoldersChangeEvent.md @@ -0,0 +1,26 @@ +# Interface: SketchFoldersChangeEvent + +An event describing a change to the set of [sketch folders](SketchFolder.md). + +## Table of contents + +### Properties + +- [addedPaths](SketchFoldersChangeEvent.md#addedpaths) +- [removedPaths](SketchFoldersChangeEvent.md#removedpaths) + +## Properties + +### addedPaths + +• `Readonly` **addedPaths**: readonly `string`[] + +Added sketch folders. + +--- + +### removedPaths + +• `Readonly` **removedPaths**: readonly `string`[] + +Removed sketch folders. diff --git a/package-lock.json b/package-lock.json index 0d82a40..f3af67f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "source-map-support": "^0.5.21", "ts-loader": "^9.4.2", "ts-node": "^10.9.2", + "typedoc": "^0.25.6", + "typedoc-plugin-markdown": "^3.17.1", "typescript": "^5.0.4", "webpack": "^5.81.0", "webpack-cli": "^5.0.2" @@ -2651,6 +2653,12 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6657,6 +6665,12 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -12658,6 +12672,18 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -13755,6 +13781,75 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.6.tgz", + "integrity": "sha512-1rdionQMpOkpA58qfym1J+YD+ukyA1IEIa4VZahQI2ZORez7dhOvEyUotQL/8rSoMBopdzOS+vAIsORpQO4cTA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", + "dev": true, + "dependencies": { + "handlebars": "^4.7.7" + }, + "peerDependencies": { + "typedoc": ">=0.24.0" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", @@ -13913,6 +14008,18 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -16283,6 +16390,12 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -19155,6 +19268,12 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -23307,6 +23426,18 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -24112,6 +24243,53 @@ "is-typedarray": "^1.0.0" } }, + "typedoc": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.6.tgz", + "integrity": "sha512-1rdionQMpOkpA58qfym1J+YD+ukyA1IEIa4VZahQI2ZORez7dhOvEyUotQL/8rSoMBopdzOS+vAIsORpQO4cTA==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "typedoc-plugin-markdown": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", + "dev": true, + "requires": { + "handlebars": "^4.7.7" + } + }, "typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", @@ -24222,6 +24400,18 @@ "spdx-expression-parse": "^3.0.0" } }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 7210ab1..72beaa8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "clean": "rimraf dist out *.vsix", "compile": "webpack && vsce package", "compile-tests": "tsc -p . --outDir out", + "doc": "typedoc --plugin typedoc-plugin-markdown --readme none --hideBreadcrumbs true --disableSources --out docs ./src/api.ts", + "postdoc": "prettier --write ./docs", "format": "prettier --write .", "lint": "eslint src --ext ts", "release": "semantic-release", @@ -110,6 +112,8 @@ "source-map-support": "^0.5.21", "ts-loader": "^9.4.2", "ts-node": "^10.9.2", + "typedoc": "^0.25.6", + "typedoc-plugin-markdown": "^3.17.1", "typescript": "^5.0.4", "webpack": "^5.81.0", "webpack-cli": "^5.0.2" diff --git a/src/api.ts b/src/api.ts index 6ddf829..51481c9 100644 --- a/src/api.ts +++ b/src/api.ts @@ -7,7 +7,7 @@ import type { Programmer, ToolsDependencies, } from 'ardunno-cli'; -import type { BoardIdentifier } from 'boards-list'; +import type { BoardIdentifier, PortIdentifier } from 'boards-list'; import type { Event } from 'vscode'; /** @@ -56,7 +56,19 @@ export interface SketchFolder { * The currently selected port in the sketch folder. * @alpha */ - readonly port: Readonly | undefined; + readonly port: Readonly | PortIdentifier | undefined; + + /** + * The currently selected programmer. + * @alpha + */ + readonly selectedProgrammer: Readonly | string | undefined; + + /** + * The FQBN with all the custom board options (if any) for the sketch. `a:b:c:opt1=value_1,opt2=value_2` means `{ "opt1": "value_1", "opt2": "value_2" }` config options are configured. + * @alpha + */ + readonly configOptions: string | undefined; } /** diff --git a/src/arduinoContext.ts b/src/arduinoContext.ts index 0dfdd1f..808f560 100644 --- a/src/arduinoContext.ts +++ b/src/arduinoContext.ts @@ -1,4 +1,8 @@ -import { isBoardIdentifier } from 'boards-list'; +import { + PortIdentifier, + isBoardIdentifier, + isPortIdentifier, +} from 'boards-list'; import assert from 'node:assert/strict'; import vscode from 'vscode'; import type { @@ -7,15 +11,14 @@ import type { BoardDetails, ChangeEvent, CliConfig, - CompileSummary, Port, + Programmer, SketchFolder, SketchFoldersChangeEvent, } from './api'; export function activateArduinoContext( context: Pick, - state: vscode.Memento, outputChannelFactory: () => vscode.OutputChannel ): ReturnType { // config @@ -40,7 +43,6 @@ export function activateArduinoContext( const options = { debug, - state, compareBeforeUpdate() { return compareBeforeUpdate; }, @@ -66,15 +68,12 @@ export function activateArduinoContext( interface CreateOptions { debug(message: string): void; compareBeforeUpdate(): boolean; - readonly state: vscode.Memento; } export function createArduinoContext( options: CreateOptions -): ArduinoContext & - vscode.Disposable & { update(args: unknown): Promise } { - const { debug, state } = options; - +): ArduinoContext & vscode.Disposable & { update(args: unknown): unknown } { + const { debug } = options; // events let disposed = false; const _onDidChangeCurrentSketch = new vscode.EventEmitter< @@ -103,10 +102,6 @@ export function createArduinoContext( throw new Error('Disposed'); } }; - const get = (key: keyof ArduinoState) => { - assertNotDisposed(); - return getState(state, key); - }; const hasChanged = (event: ChangeEvent, currentObject: T | undefined) => event.changedProperties.some((property) => { const newValue = event.object[property]; @@ -137,18 +132,19 @@ export function createArduinoContext( let _cliConfig: CliConfig | undefined = undefined; let _currentSketch: SketchFolder | undefined = undefined; - const updateCliConfig = async (params: UpdateCliConfigParams) => { + const updateCliConfig = (params: UpdateCliConfigParams) => { const changed = !options.compareBeforeUpdate() || hasChanged(params, _cliConfig); if (changed) { - // TODO: remove old notifications - await Promise.all( - params.changedProperties.map((property) => - update(property, params.object[property]) - ) - ); - + // update value _cliConfig = params.object; + + // TODO: remove old notifications + // emit deprecated events + for (const property of params.changedProperties) { + emitDeprecatedEvent(property, params.object[property]); + } + // emit new API events _onDidChangeConfig.fire(params); } }; @@ -218,65 +214,72 @@ export function createArduinoContext( removedPaths: params.removedPaths, }); }; - const updateCurrentSketch = async (params: UpdateCurrentSketchParams) => { + const updateCurrentSketch = (params: UpdateCurrentSketchParams) => { assertIsOpened(params.currentSketch); - await update('sketchPath', params.currentSketch?.sketchPath); + emitDeprecatedEvent('sketchPath', params.currentSketch?.sketchPath); _currentSketch = params.currentSketch; _onDidChangeCurrentSketch.fire(_currentSketch); }; - const updateSketch = async (params: UpdateSketchParam) => { + const updateSketch = (params: UpdateSketchParam) => { assertIsOpened(params.object); const changed = !options.compareBeforeUpdate() || hasChanged(params, _currentSketch); if (changed) { + // set value + _currentSketch = params.object; + + // emit deprecated events // TODO: remove old notifications - await Promise.all( - params.changedProperties.map((property) => { - // backward compatibility between board vs. fqbn+boardDetails - if (property === 'board') { - const newValue = params.object[property]; - if (!newValue) { - return Promise.all([ - update('fqbn', undefined), - update('boardDetails', undefined), - ]); - } else if (isBoardDetails(newValue)) { - return Promise.all([ - update('fqbn', newValue.fqbn), - update('boardDetails', newValue), - ]); - } else { - return Promise.all([ - update('fqbn', newValue.fqbn), - update('boardDetails', undefined), - ]); - } + for (const property of params.changedProperties) { + if (property === 'selectedProgrammer' || property === 'configOptions') { + // https://github.com/dankeboy36/vscode-arduino-api/issues/13 + // new properties are not required for old APIs + continue; + } + + // backward compatibility between board vs. fqbn+boardDetails + if (property === 'board') { + const newValue = params.object[property]; + if (!newValue) { + emitDeprecatedEvent('fqbn', undefined); + emitDeprecatedEvent('boardDetails', undefined); + } else if (isBoardDetails(newValue)) { + emitDeprecatedEvent('fqbn', newValue.fqbn); + emitDeprecatedEvent('boardDetails', newValue); + } else { + emitDeprecatedEvent('fqbn', newValue.fqbn); + emitDeprecatedEvent('boardDetails', undefined); } - return update(property, params.object[property]); - }) - ); + continue; + } + + if (property === 'port') { + const port = params.object.port; + // https://github.com/dankeboy36/vscode-arduino-api/issues/13 + // if the new port value is set but not a complete detected port object, skip the update + if (!port || isDetectedPort(port)) { + emitDeprecatedEvent(property, port); + } else { + emitDeprecatedEvent('port', undefined); + } + } else { + emitDeprecatedEvent(property, params.object[property]); + } + } - _currentSketch = params.object; + // emit new events _onDidChangeSketch.fire(params); } }; /** @deprecated will be removed */ - const update = async ( + const emitDeprecatedEvent = ( key: keyof ArduinoState, - value: ArduinoState[keyof ArduinoState] + newValue: ArduinoState[keyof ArduinoState] ) => { assertNotDisposed(); - if (options.compareBeforeUpdate()) { - const currentValue = get(key); - try { - assert.deepStrictEqual(currentValue, value); - return; - } catch {} - } - await updateState(state, key, value); - debug(`Updated '${key}': ${JSON.stringify(value)}`); - emitters[key].fire(value); + debug(`Updated '${key}': ${JSON.stringify(newValue)}`); + emitters[key].fire(newValue); }; // context @@ -285,25 +288,33 @@ export function createArduinoContext( return onDidChange[property] as vscode.Event; }, get sketchPath() { - return get('sketchPath'); + return _currentSketch?.sketchPath; }, get compileSummary() { - return get('compileSummary'); + return _currentSketch?.compileSummary; }, get fqbn() { - return get('fqbn'); + return _currentSketch?.board?.fqbn; }, get boardDetails() { - return get('boardDetails'); + const board = _currentSketch?.board; + if (board && !isBoardDetails(board)) { + return undefined; + } + return board; }, get port() { - return get('port'); + const port = _currentSketch?.port; + if (port && !isDetectedPort(port)) { + return undefined; + } + return port; }, get userDirPath() { - return get('userDirPath'); + return _cliConfig?.userDirPath; }, get dataDirPath() { - return get('dataDirPath'); + return _cliConfig?.dataDirPath; }, dispose(): void { if (disposed) { @@ -326,7 +337,7 @@ export function createArduinoContext( get openedSketches() { return _openedSketches; }, - update: async function (args: unknown) { + update: function (args: unknown) { assertNotDisposed(); if (isUpdateCliConfigParams(args)) { return updateCliConfig(args); @@ -379,21 +390,6 @@ function createEmitters(): Record< }, >>{}); } -function getState( - state: vscode.Memento, - key: keyof ArduinoContext -): T | undefined { - return state.get(key); -} - -async function updateState( - state: vscode.Memento, - key: keyof ArduinoState, - value: ArduinoState[keyof ArduinoState] -): Promise { - return state.update(key, value); -} - type UpdateCliConfigParams = ChangeEvent; function isCliConfig(arg: unknown): arg is CliConfig { @@ -437,10 +433,31 @@ function isSketchFolder(arg: unknown): arg is SketchFolder { typeof (arg).compileSummary === 'object') && 'port' in arg && ((arg).port === undefined || - typeof (arg).port === 'object') + typeof (arg).port === 'object') && + 'selectedProgrammer' in arg && + ((arg).selectedProgrammer === undefined || + typeof (arg).selectedProgrammer === 'string' || + isProgrammer((arg).selectedProgrammer)) && + 'configOptions' in arg && + ((arg).configOptions === undefined || + typeof (arg).configOptions === 'string') ); } +function isProgrammer(arg: unknown): arg is Programmer { + return ( + typeof arg === 'object' && + arg !== null && + typeof (arg).id === 'string' && + typeof (arg).name === 'string' && + typeof (arg).platform === 'string' + ); +} + +function isDetectedPort(port: PortIdentifier | Port): port is Port { + return isPortIdentifier(port) && typeof (port).label === 'string'; +} + function isBoardDetails(arg: unknown): arg is BoardDetails { return ( isBoardIdentifier(arg) && diff --git a/src/extension.ts b/src/extension.ts index fb7fc0d..1585207 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,12 +1,9 @@ import vscode from 'vscode'; import { activateArduinoContext } from './arduinoContext'; -import { InmemoryState } from './inmemoryState'; export function activate(context: vscode.ExtensionContext) { - const arduinoContext = activateArduinoContext( - context, - new InmemoryState(), // TODO: persist to the `workspaceState`? - () => vscode.window.createOutputChannel('Arduino API') + const arduinoContext = activateArduinoContext(context, () => + vscode.window.createOutputChannel('Arduino API') ); context.subscriptions.push( arduinoContext, diff --git a/src/inmemoryState.ts b/src/inmemoryState.ts deleted file mode 100644 index e7c771c..0000000 --- a/src/inmemoryState.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Memento } from 'vscode'; - -export class InmemoryState implements Memento { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly state: Record; - - constructor() { - this.state = {}; - } - - keys(): readonly string[] { - return Object.keys(this.state); - } - - get(key: string): T | undefined; - get(key: string, defaultValue: T): T; - get(key: string, defaultValue?: unknown): T | undefined { - return this.state[key] ?? defaultValue; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - update(key: string, value: any): Thenable { - if (value === undefined) { - delete this.state[key]; - } else { - this.state[key] = value; - } - return Promise.resolve(); - } -} diff --git a/src/test/suite/arduinoContext.test.ts b/src/test/suite/arduinoContext.test.ts index 1ae39bf..f1a1186 100644 --- a/src/test/suite/arduinoContext.test.ts +++ b/src/test/suite/arduinoContext.test.ts @@ -9,6 +9,7 @@ import type { CliConfig, CompileSummary, Port, + Programmer, SketchFolder, SketchFoldersChangeEvent, } from '../../api'; @@ -17,7 +18,6 @@ import { activateArduinoContext, createArduinoContext, } from '../../arduinoContext'; -import { InmemoryState } from '../../inmemoryState'; const { defaultConfigValues, @@ -40,20 +40,24 @@ const port: Port = { }, hardwareId: '1730323', }; + +const programmer: Programmer = { + id: 'p1', + name: 'P1 Programmer', + platform: 'my platform', +}; + const boardDetails: BoardDetails = { configOptions: [], fqbn: 'a:b:c', name: 'ABC', - programmers: [ - { id: 'one', name: 'one', platform: 'one' }, - { id: 'two', name: 'two', platform: 'two' }, - ], + programmers: [programmer, { id: 'two', name: 'two', platform: 'two' }], toolsDependencies: [ { name: 'a', packager: 'a', version: '1' }, { name: 'b', packager: 'b', version: '2' }, ], buildProperties: { 'build.tarch': 'xtensa', x: 'y' }, - defaultProgrammerId: 'two', + defaultProgrammerId: programmer.id, }; const compileSummary: CompileSummary = { @@ -70,6 +74,8 @@ const sketchFolder: SketchFolder = { compileSummary, port, sketchPath: '/path/to/sketchbook/my_sketch', + selectedProgrammer: programmer, + configOptions: 'a:b:c:opt_1=value1,opt_2=value2', }; const initSketchFoldersChangeEvent: SketchFoldersChangeEvent & @@ -228,14 +234,13 @@ describe('arduinoContext', () => { const copy: Record = { ...sketchFolder, }; - if (typeof copy[property] === 'string') { + if ((property as keyof SketchFolder) === 'selectedProgrammer') { + copy[property] = 36; // selectedProgrammer can be object and string + } else if (typeof copy[property] === 'string') { copy[property] = { alma: 'korte' }; } else { copy[property] = 'alma'; } - if (property === 'board') { - console.log(); - } assert.strictEqual(isSketchFolder(copy), false); }) ); @@ -253,17 +258,17 @@ describe('arduinoContext', () => { assert.ok(typeof context.update === 'function'); }); - it('should update the data directory path', async () => { + it('should update the data directory path', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); assert.strictEqual(context.dataDirPath, undefined); assert.strictEqual(context.config.dataDirPath, undefined); - const config: CliConfig = { + const config = { dataDirPath: '/path/to/data/dir', userDirPath: undefined, }; - const event: ChangeEvent = { + const event = { changedProperties: ['dataDirPath'], object: config, }; @@ -281,7 +286,7 @@ describe('arduinoContext', () => { ) ); - await context.update(event); + context.update(event); assert.strictEqual(context.dataDirPath, '/path/to/data/dir'); assert.strictEqual(context.userDirPath, undefined); assert.strictEqual(context.config.dataDirPath, '/path/to/data/dir'); @@ -289,17 +294,17 @@ describe('arduinoContext', () => { assert.deepStrictEqual(events, ['deprecated-dataDirPath', 'dataDirPath']); }); - it('should update the user directory path', async () => { + it('should update the user directory path', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); assert.strictEqual(context.userDirPath, undefined); assert.strictEqual(context.config.userDirPath, undefined); - const config: CliConfig = { + const config = { dataDirPath: undefined, userDirPath: '/path/to/sketchbook', }; - const event: ChangeEvent = { + const event = { changedProperties: ['userDirPath'], object: config, }; @@ -317,7 +322,7 @@ describe('arduinoContext', () => { ) ); - await context.update(event); + context.update(event); assert.strictEqual(context.dataDirPath, undefined); assert.strictEqual(context.userDirPath, '/path/to/sketchbook'); assert.strictEqual(context.config.dataDirPath, undefined); @@ -325,7 +330,7 @@ describe('arduinoContext', () => { assert.deepStrictEqual(events, ['deprecated-userDirPath', 'userDirPath']); }); - it('should update data and the user directory paths', async () => { + it('should update data and the user directory paths', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); assert.strictEqual(context.dataDirPath, undefined); @@ -333,11 +338,11 @@ describe('arduinoContext', () => { assert.strictEqual(context.userDirPath, undefined); assert.strictEqual(context.config.userDirPath, undefined); - const config: CliConfig = { + const config = { dataDirPath: '/path/to/data/dir', userDirPath: '/path/to/sketchbook', }; - const event: ChangeEvent = { + const event = { changedProperties: ['userDirPath', 'dataDirPath'], object: config, }; @@ -355,7 +360,7 @@ describe('arduinoContext', () => { ) ); - await context.update(event); + context.update(event); assert.strictEqual(context.dataDirPath, '/path/to/data/dir'); assert.strictEqual(context.userDirPath, '/path/to/sketchbook'); assert.strictEqual(context.config.dataDirPath, '/path/to/data/dir'); @@ -368,7 +373,7 @@ describe('arduinoContext', () => { ]); }); - it("should not update when 'compareBeforeUpdate' is true and values are the same", async () => { + it("should not update when 'compareBeforeUpdate' is true and values are the same", () => { const context = createArduinoContext(createOptions()); toDispose.push(context); assert.strictEqual(context.dataDirPath, undefined); @@ -382,7 +387,7 @@ describe('arduinoContext', () => { changedProperties: ['dataDirPath'], object: config, }; - await context.update(event); + context.update(event); assert.strictEqual(context.dataDirPath, '/path/to/data/dir'); assert.strictEqual(context.config.dataDirPath, '/path/to/data/dir'); @@ -396,22 +401,22 @@ describe('arduinoContext', () => { ) ); - await context.update(event); + context.update(event); assert.strictEqual(context.dataDirPath, '/path/to/data/dir'); assert.strictEqual(context.config.dataDirPath, '/path/to/data/dir'); assert.deepStrictEqual(events, []); }); - it('should error when updating the current sketch but is not opened', async () => { + it('should error when updating the current sketch but is not opened', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); - await assert.rejects( - context.update({ currentSketch: sketchFolder }), + assert.throws( + () => context.update({ currentSketch: sketchFolder }), /Error: Illegal state. Sketch is not opened/ ); }); - it('should error when updating sketch folders with invalid params (added/remove must be distinct)', async () => { + it('should error when updating sketch folders with invalid params (added/remove must be distinct)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const params = { @@ -419,13 +424,13 @@ describe('arduinoContext', () => { openedSketches: [], removedPaths: ['path'], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal argument. Added\/removed paths must be distinct/ ); }); - it('should error when updating sketch folders with invalid params (sketch paths must be unique)', async () => { + it('should error when updating sketch folders with invalid params (sketch paths must be unique)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const params = { @@ -433,13 +438,13 @@ describe('arduinoContext', () => { openedSketches: [sketchFolder, sketchFolder], removedPaths: [], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal argument. Sketch paths must be unique/ ); }); - it('should error when updating sketch folders with invalid params (added path is not in new opened)', async () => { + it('should error when updating sketch folders with invalid params (added path is not in new opened)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const params = { @@ -447,13 +452,13 @@ describe('arduinoContext', () => { openedSketches: [], removedPaths: [], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal argument. Added path must be in opened sketches/ ); }); - it('should error when updating sketch folders with invalid params (removed path is in new opened)', async () => { + it('should error when updating sketch folders with invalid params (removed path is in new opened)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const params = { @@ -461,13 +466,13 @@ describe('arduinoContext', () => { openedSketches: [{ ...sketchFolder, sketchPath: 'path' }], removedPaths: ['path'], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal argument. Removed path must not be in opened sketches/ ); }); - it('should error when updating sketch folders with invalid state (removed path is not in current opened)', async () => { + it('should error when updating sketch folders with invalid state (removed path is not in current opened)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const params = { @@ -475,52 +480,58 @@ describe('arduinoContext', () => { openedSketches: [], removedPaths: ['path'], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal state update. Removed sketch folder was not opened/ ); }); - it('should error when updating sketch folders with invalid state (added path is already in current opened)', async () => { + it('should error when updating sketch folders with invalid state (added path is already in current opened)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); - await context.update(initSketchFoldersChangeEvent); // open a sketch folder + context.update(initSketchFoldersChangeEvent); // open a sketch folder const params = { addedPaths: [sketchFolder.sketchPath], openedSketches: [sketchFolder], removedPaths: [], }; - await assert.rejects( - context.update(params), + assert.throws( + () => context.update(params), /Error: Illegal state update. Added sketch folder was already opened/ ); }); - it('should error when updating with invalid params', async () => { + it('should error when updating with invalid params', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); - await assert.rejects( - context.update({ manó: '♥' }), + assert.throws( + () => context.update({ manó: '♥' }), /Invalid params: {"manó":"♥"}/ ); }); - it('should gracefully handle when updating with invalid params (non-JSON serializable)', async () => { + it('should gracefully handle when updating with invalid params (non-JSON serializable)', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); const circular = { b: 1, a: 0 }; // eslint-disable-next-line @typescript-eslint/no-explicit-any (circular as any).circular = circular; - await assert.rejects( - context.update(circular), + assert.throws( + () => context.update(circular), /Invalid params: \[object Object\]/ ); }); - interface UpdateSketchTestInput { - property: keyof SketchFolder; - value: SketchFolder[keyof SketchFolder]; + interface UpdateSketchTestInput< + T extends keyof SketchFolder = keyof SketchFolder + > { + property: T; + inputValue: SketchFolder[T]; + /** + * If not set, the `inputValue` is used as the expected. Use this while the deprecated APIs are in place. + */ + expectedValue?: SketchFolder[T]; } interface UpdateSketchTest { inputs: UpdateSketchTestInput[]; @@ -529,21 +540,32 @@ describe('arduinoContext', () => { } const updateTests: UpdateSketchTest[] = [ { - inputs: [{ property: 'board', value: undefined }], - expectedEvents: [`sketch:${sketchFolder.sketchPath}:board`], + inputs: [{ property: 'board', inputValue: undefined }], + expectedEvents: [ + 'deprecated-fqbn', + 'deprecated-boardDetails', + `sketch:${sketchFolder.sketchPath}:board`, + ], testNote: 'undefined', }, { inputs: [ - { property: 'board', value: { name: 'x:y:z', fqbn: undefined } }, + { property: 'board', inputValue: { name: 'x:y:z', fqbn: undefined } }, + ], + expectedEvents: [ + 'deprecated-fqbn', + 'deprecated-boardDetails', + `sketch:${sketchFolder.sketchPath}:board`, ], - expectedEvents: [`sketch:${sketchFolder.sketchPath}:board`], testNote: 'absent fqbn', }, { - inputs: [{ property: 'board', value: { name: 'XYZ', fqbn: 'x:y:z' } }], + inputs: [ + { property: 'board', inputValue: { name: 'XYZ', fqbn: 'x:y:z' } }, + ], expectedEvents: [ 'deprecated-fqbn', + 'deprecated-boardDetails', `sketch:${sketchFolder.sketchPath}:board`, ], testNote: 'missing platform', @@ -552,7 +574,7 @@ describe('arduinoContext', () => { inputs: [ { property: 'board', - value: { ...boardDetails, name: 'XYZ', fqbn: 'x:y:z' }, + inputValue: { ...boardDetails, name: 'XYZ', fqbn: 'x:y:z' }, }, ], expectedEvents: [ @@ -563,15 +585,18 @@ describe('arduinoContext', () => { testNote: 'complete details', }, { - inputs: [{ property: 'compileSummary', value: undefined }], - expectedEvents: [`sketch:${sketchFolder.sketchPath}:compileSummary`], + inputs: [{ property: 'compileSummary', inputValue: undefined }], + expectedEvents: [ + 'deprecated-compileSummary', + `sketch:${sketchFolder.sketchPath}:compileSummary`, + ], testNote: 'undefined', }, { inputs: [ { property: 'compileSummary', - value: { + inputValue: { ...compileSummary, buildProperties: { ...compileSummary.buildProperties, @@ -586,30 +611,89 @@ describe('arduinoContext', () => { ], }, { - inputs: [{ property: 'port', value: undefined }], - expectedEvents: [`sketch:${sketchFolder.sketchPath}:port`], + inputs: [{ property: 'port', inputValue: undefined }], + expectedEvents: [ + 'deprecated-port', + `sketch:${sketchFolder.sketchPath}:port`, + ], testNote: 'undefined', }, { inputs: [ { property: 'port', - value: { ...port, address: 'COM2', label: 'COM2 (Serial Port)' }, + inputValue: { + ...port, + address: 'COM2', + label: 'COM2 (Serial Port)', + }, + }, + ], + expectedEvents: [ + 'deprecated-port', + `sketch:${sketchFolder.sketchPath}:port`, + ], + }, + { + inputs: [ + { + property: 'port', + inputValue: { protocol: port.protocol, address: 'COM2' }, + expectedValue: undefined, }, ], expectedEvents: [ 'deprecated-port', `sketch:${sketchFolder.sketchPath}:port`, ], + testNote: '(port identifier is undefined via deprecated APIs)', + }, + { + inputs: [ + { + property: 'selectedProgrammer', + inputValue: 'p2', + }, + ], + expectedEvents: [ + `sketch:${sketchFolder.sketchPath}:selectedProgrammer`, + ], + testNote: 'string', + }, + { + inputs: [ + { + property: 'selectedProgrammer', + inputValue: { + id: 'xxxp', + name: 'XXX Programmer', + platform: 'my_platform', + }, + }, + ], + expectedEvents: [ + `sketch:${sketchFolder.sketchPath}:selectedProgrammer`, + ], + testNote: 'object', + }, + { + inputs: [ + { + property: 'selectedProgrammer', + inputValue: undefined, + }, + ], + expectedEvents: [ + `sketch:${sketchFolder.sketchPath}:selectedProgrammer`, + ], + testNote: 'undefined', }, ]; updateTests.map(({ inputs, expectedEvents, testNote }) => it(`should update the ${inputs .map(({ property }) => `'${property}'`) - .join(', ')} of the sketch ${ - testNote ? `(${testNote})` : '' - }`, async () => { + .join(', ')} of the sketch ${testNote ? `(${testNote})` : ''}`, () => { const events: string[] = []; const context = createArduinoContext(createOptions()); toDispose.push(context); @@ -619,6 +703,12 @@ describe('arduinoContext', () => { /** @deprecated will be removed */ const deprecatedEvents: vscode.Disposable[] = []; for (const property of inputs.map(({ property }) => property)) { + if ( + property === 'selectedProgrammer' || + property === 'configOptions' + ) { + continue; + } // backward compatibility if (property === 'board') { deprecatedEvents.push( @@ -651,38 +741,44 @@ describe('arduinoContext', () => { ); // Open sketch folders - await context.update(initSketchFoldersChangeEvent); + context.update(initSketchFoldersChangeEvent); // Select the current one - await context.update({ currentSketch: sketchFolder }); + context.update({ currentSketch: sketchFolder }); assert.deepStrictEqual(context.currentSketch, sketchFolder); assert.deepStrictEqual(context.openedSketches, [sketchFolder]); assert.strictEqual(context.sketchPath, sketchFolder.sketchPath); - for (const { property, value } of inputs) { - assert.notDeepStrictEqual(context.currentSketch?.[property], value); - + for (const input of inputs) { + const { property, inputValue } = input; const copy = { ...sketchFolder }; - (copy as Record)[property] = value; + (copy as Record)[property] = inputValue; const params: ChangeEvent = { object: copy, changedProperties: [property], }; - await context.update(params); - assert.deepStrictEqual(context.currentSketch?.[property], value); + context.update(params); + assert.deepStrictEqual(context.currentSketch?.[property], inputValue); + const expectedValue = + 'expectedValue' in input ? input.expectedValue : inputValue; // to test backward compatibility - if (property === 'board') { - if (isBoardDetails(value)) { - assert.deepStrictEqual(context.boardDetails, value); - } else if (isBoardIdentifier(value)) { - assert.deepStrictEqual(context.fqbn, value.fqbn); + if ( + property !== 'selectedProgrammer' && + property !== 'configOptions' + ) { + if (property === 'board') { + if (isBoardDetails(expectedValue)) { + assert.deepStrictEqual(context.boardDetails, expectedValue); + } else if (isBoardIdentifier(expectedValue)) { + assert.deepStrictEqual(context.fqbn, expectedValue.fqbn); + } else { + assert.deepStrictEqual(context.fqbn, expectedValue); + assert.deepStrictEqual(context.boardDetails, expectedValue); + } } else { - assert.deepStrictEqual(context.fqbn, value); - assert.deepStrictEqual(context.boardDetails, value); + assert.deepStrictEqual(context[property], expectedValue); } - } else { - assert.deepStrictEqual(context[property], value); } } @@ -753,22 +849,21 @@ describe('arduinoContext', () => { const context = activateArduinoContext( mockExtensionContext, - new InmemoryState(), mockOutputChannelFactory ); toDispose.push(context, ...subscriptions); await updateWorkspaceConfig('log', false); - await context.update(initSketchFoldersChangeEvent); - await context.update({ currentSketch: sketchFolder }); + context.update(initSketchFoldersChangeEvent); + context.update({ currentSketch: sketchFolder }); assert.deepStrictEqual(context.currentSketch?.board, sketchFolder.board); let params: ChangeEvent = { object: { ...sketchFolder, board: { name: 'XYZ', fqbn: 'x:y:z' } }, changedProperties: ['board'], }; - await context.update(params); + context.update(params); assert.deepStrictEqual(context.currentSketch?.board, { name: 'XYZ', fqbn: 'x:y:z', @@ -780,7 +875,7 @@ describe('arduinoContext', () => { object: { ...params.object, board: { name: 'QWE', fqbn: 'q:w:e' } }, changedProperties: ['board'], }; - await context.update(params); + context.update(params); assert.deepStrictEqual(context.currentSketch?.board, { name: 'QWE', fqbn: 'q:w:e', @@ -811,15 +906,14 @@ describe('arduinoContext', () => { const context = activateArduinoContext( mockExtensionContext, - new InmemoryState(), mockOutputChannelFactory ); toDispose.push(context, ...subscriptions); await updateWorkspaceConfig('compareBeforeUpdate', true); - await context.update(initSketchFoldersChangeEvent); - await context.update({ currentSketch: sketchFolder }); + context.update(initSketchFoldersChangeEvent); + context.update({ currentSketch: sketchFolder }); assert.deepStrictEqual(context.currentSketch?.board, sketchFolder.board); const events: string[] = []; @@ -837,11 +931,11 @@ describe('arduinoContext', () => { object: sketchFolder, changedProperties: ['board'], }; - await context.update(params); + context.update(params); assert.deepStrictEqual(events, []); await updateWorkspaceConfig('compareBeforeUpdate', false); - await context.update(params); + context.update(params); assert.deepStrictEqual(events, [ 'deprecated-fqbn', 'deprecated-boardDetails', @@ -860,13 +954,13 @@ describe('arduinoContext', () => { }); describe('dispose', () => { - it('should error when updating after dispose', async () => { + it('should error when updating after dispose', () => { const context = createArduinoContext(createOptions()); toDispose.push(context); context.dispose(); - await assert.rejects( - context.update({ currentSketch: sketchFolder }), + assert.throws( + () => context.update({ currentSketch: sketchFolder }), /Disposed/ ); }); @@ -886,7 +980,6 @@ describe('arduinoContext', () => { /* NOOP */ }, compareBeforeUpdate: () => compare, - state: new InmemoryState(), }; } }); diff --git a/src/test/suite/inmemoryState.test.ts b/src/test/suite/inmemoryState.test.ts deleted file mode 100644 index dfe8bb0..0000000 --- a/src/test/suite/inmemoryState.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import assert from 'node:assert/strict'; -import { InmemoryState } from '../../inmemoryState'; - -describe('inmemoryState', () => { - it('should get a value', async () => { - const state = new InmemoryState(); - await state.update('alma', false); - assert.strictEqual(state.get('alma'), false); - }); - - it('should get a value with a default', async () => { - const state = new InmemoryState(); - assert.strictEqual(state.get('alma'), undefined); - assert.strictEqual(state.get('alma', false), false); - }); - - it('should not modify the state when getting with default and the cache hits a miss', async () => { - const state = new InmemoryState(); - assert.strictEqual(state.get('alma', false), false); - const actual = state.keys(); - assert.strictEqual(actual.length, 0); - }); - - it('should retrieve the keys', async () => { - const state = new InmemoryState(); - await state.update('alma', false); - await state.update('korte', 36); - const actual = state.keys(); - assert.strictEqual(actual.length, 2); - assert.strictEqual(actual.includes('alma'), true); - assert.strictEqual(actual.includes('korte'), true); - }); - - it('should update with new value', async () => { - const state = new InmemoryState(); - await state.update('alma', false); - assert.strictEqual(state.get('alma'), false); - await state.update('alma', 36); - assert.strictEqual(state.get('alma'), 36); - const actual = state.keys(); - assert.strictEqual(actual.length, 1); - assert.strictEqual(actual.includes('alma'), true); - }); - - it('should remove when the update value is undefined', async () => { - const state = new InmemoryState(); - await state.update('alma', false); - assert.strictEqual(state.get('alma'), false); - await state.update('alma', undefined); - assert.strictEqual(state.get('alma'), undefined); - const actual = state.keys(); - assert.strictEqual(actual.length, 0); - }); -});