Browse Source

chore: type check JS in docs (#38423)

* build(deps): update @electron/lint-roller

* chore: type check JS in docs

* docs: add @ts-check and @ts-expect-error to code blocks

* chore: fix type check errors in docs

* chore: add ts-type to blocks
David Sanders 1 year ago
parent
commit
905aad9cb6
49 changed files with 256 additions and 181 deletions
  1. 4 4
      docs/api/app.md
  2. 8 1
      docs/api/browser-window.md
  3. 2 2
      docs/api/client-request.md
  4. 1 4
      docs/api/clipboard.md
  5. 2 2
      docs/api/context-bridge.md
  6. 4 2
      docs/api/desktop-capturer.md
  7. 2 2
      docs/api/dialog.md
  8. 2 2
      docs/api/incoming-message.md
  9. 2 2
      docs/api/ipc-main.md
  10. 1 1
      docs/api/ipc-renderer.md
  11. 3 3
      docs/api/menu.md
  12. 4 3
      docs/api/message-channel-main.md
  13. 1 1
      docs/api/net-log.md
  14. 9 9
      docs/api/protocol.md
  15. 29 27
      docs/api/session.md
  16. 4 4
      docs/api/touch-bar.md
  17. 29 18
      docs/api/web-contents.md
  18. 2 1
      docs/api/web-frame-main.md
  19. 1 2
      docs/api/web-frame.md
  20. 5 5
      docs/api/webview-tag.md
  21. 1 1
      docs/tutorial/asar-archives.md
  22. 1 1
      docs/tutorial/asar-integrity.md
  23. 8 8
      docs/tutorial/automated-testing.md
  24. 3 3
      docs/tutorial/code-signing.md
  25. 4 4
      docs/tutorial/context-isolation.md
  26. 1 1
      docs/tutorial/dark-mode.md
  27. 1 1
      docs/tutorial/fuses.md
  28. 1 1
      docs/tutorial/installation.md
  29. 8 8
      docs/tutorial/ipc.md
  30. 2 2
      docs/tutorial/keyboard-shortcuts.md
  31. 5 3
      docs/tutorial/launch-app-from-url-in-another-app.md
  32. 4 4
      docs/tutorial/message-ports.md
  33. 1 1
      docs/tutorial/multithreading.md
  34. 2 1
      docs/tutorial/native-file-drag-drop.md
  35. 3 3
      docs/tutorial/performance.md
  36. 3 3
      docs/tutorial/process-model.md
  37. 3 2
      docs/tutorial/quick-start.md
  38. 8 7
      docs/tutorial/security.md
  39. 1 1
      docs/tutorial/snapcraft.md
  40. 8 8
      docs/tutorial/spellchecker.md
  41. 2 2
      docs/tutorial/tray.md
  42. 2 2
      docs/tutorial/tutorial-2-first-app.md
  43. 2 2
      docs/tutorial/tutorial-3-preload.md
  44. 1 1
      docs/tutorial/tutorial-6-publishing-updating.md
  45. 2 2
      docs/tutorial/updates.md
  46. 2 2
      docs/tutorial/window-customization.md
  47. 5 5
      docs/tutorial/windows-taskbar.md
  48. 4 2
      package.json
  49. 53 5
      yarn.lock

+ 4 - 4
docs/api/app.md

@@ -971,7 +971,7 @@ app.setJumpList([
         title: 'Tool A',
         program: process.execPath,
         args: '--run-tool-a',
-        icon: process.execPath,
+        iconPath: process.execPath,
         iconIndex: 0,
         description: 'Runs Tool A'
       },
@@ -980,7 +980,7 @@ app.setJumpList([
         title: 'Tool B',
         program: process.execPath,
         args: '--run-tool-b',
-        icon: process.execPath,
+        iconPath: process.execPath,
         iconIndex: 0,
         description: 'Runs Tool B'
       }
@@ -1418,8 +1418,8 @@ const fs = require('fs')
 let filepath
 let bookmark
 
-dialog.showOpenDialog(null, { securityScopedBookmarks: true }, (filepaths, bookmarks) => {
-  filepath = filepaths[0]
+dialog.showOpenDialog(null, { securityScopedBookmarks: true }).then(({ filePaths, bookmarks }) => {
+  filepath = filePaths[0]
   bookmark = bookmarks[0]
   fs.readFileSync(filepath)
 })

+ 8 - 1
docs/api/browser-window.md

@@ -104,6 +104,7 @@ window, you have to set both `parent` and `modal` options:
 ```javascript
 const { BrowserWindow } = require('electron')
 
+const top = new BrowserWindow()
 const child = new BrowserWindow({ parent: top, modal: true, show: false })
 child.loadURL('https://github.com')
 child.once('ready-to-show', () => {
@@ -597,7 +598,7 @@ On Linux the setter is a no-op, although the getter returns `true`.
 
 A `boolean` property that determines whether the window is excluded from the application’s Windows menu. `false` by default.
 
-```js
+```js @ts-expect-error=[11]
 const win = new BrowserWindow({ height: 600, width: 600 })
 
 const template = [
@@ -1200,6 +1201,9 @@ Node's [`url.format`](https://nodejs.org/api/url.html#url_url_format_urlobject)
 method:
 
 ```javascript
+const { BrowserWindow } = require('electron')
+const win = new BrowserWindow()
+
 const url = require('url').format({
   protocol: 'file',
   slashes: true,
@@ -1213,6 +1217,9 @@ You can load a URL using a `POST` request with URL-encoded data by doing
 the following:
 
 ```javascript
+const { BrowserWindow } = require('electron')
+const win = new BrowserWindow()
+
 win.loadURL('http://localhost:8000/post', {
   postData: [{
     type: 'rawData',

+ 2 - 2
docs/api/client-request.md

@@ -104,7 +104,7 @@ The `callback` function is expected to be called back with user credentials:
 * `username` string
 * `password` string
 
-```javascript
+```javascript @ts-type={request:Electron.ClientRequest}
 request.on('login', (authInfo, callback) => {
   callback('username', 'password')
 })
@@ -113,7 +113,7 @@ request.on('login', (authInfo, callback) => {
 Providing empty credentials will cancel the request and report an authentication
 error on the response object:
 
-```javascript
+```javascript @ts-type={request:Electron.ClientRequest}
 request.on('response', (response) => {
   console.log(`STATUS: ${response.statusCode}`)
   response.on('error', (error) => {

+ 1 - 4
docs/api/clipboard.md

@@ -148,10 +148,7 @@ clipboard.
 ```js
 const { clipboard } = require('electron')
 
-clipboard.writeBookmark({
-  text: 'https://electronjs.org',
-  bookmark: 'Electron Homepage'
-})
+clipboard.writeBookmark('Electron Homepage', 'https://electronjs.org')
 ```
 
 ### `clipboard.readFindText()` _macOS_

+ 2 - 2
docs/api/context-bridge.md

@@ -18,7 +18,7 @@ contextBridge.exposeInMainWorld(
 )
 ```
 
-```javascript
+```javascript @ts-nocheck
 // Renderer (Main World)
 
 window.electron.doThing()
@@ -104,7 +104,7 @@ contextBridge.exposeInIsolatedWorld(
 )
 ```
 
-```javascript
+```javascript @ts-nocheck
 // Renderer (In isolated world id1004)
 
 window.electron.doThing()

+ 4 - 2
docs/api/desktop-capturer.md

@@ -10,7 +10,9 @@ title is `Electron`:
 
 ```javascript
 // In the main process.
-const { desktopCapturer } = require('electron')
+const { BrowserWindow, desktopCapturer } = require('electron')
+
+const mainWindow = new BrowserWindow()
 
 desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
   for (const source of sources) {
@@ -22,7 +24,7 @@ desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources =
 })
 ```
 
-```javascript
+```javascript @ts-nocheck
 // In the preload script.
 const { ipcRenderer } = require('electron')
 

+ 2 - 2
docs/api/dialog.md

@@ -72,7 +72,7 @@ and a directory selector, so if you set `properties` to
 `['openFile', 'openDirectory']` on these platforms, a directory selector will be
 shown.
 
-```js
+```js @ts-type={mainWindow:Electron.BrowserWindow}
 dialog.showOpenDialogSync(mainWindow, {
   properties: ['openFile', 'openDirectory']
 })
@@ -139,7 +139,7 @@ and a directory selector, so if you set `properties` to
 `['openFile', 'openDirectory']` on these platforms, a directory selector will be
 shown.
 
-```js
+```js @ts-type={mainWindow:Electron.BrowserWindow}
 dialog.showOpenDialog(mainWindow, {
   properties: ['openFile', 'openDirectory']
 }).then(result => {

+ 2 - 2
docs/api/incoming-message.md

@@ -89,7 +89,7 @@ tuples. So, the even-numbered offsets are key values, and the odd-numbered
 offsets are the associated values. Header names are not lowercased, and
 duplicates are not merged.
 
-```javascript
+```javascript @ts-type={response:Electron.IncomingMessage}
 // Prints something like:
 //
 // [ 'user-agent',
@@ -100,5 +100,5 @@ duplicates are not merged.
 //   '127.0.0.1:8000',
 //   'ACCEPT',
 //   '*/*' ]
-console.log(request.rawHeaders)
+console.log(response.rawHeaders)
 ```

+ 2 - 2
docs/api/ipc-main.md

@@ -83,14 +83,14 @@ If `listener` returns a Promise, the eventual result of the promise will be
 returned as a reply to the remote caller. Otherwise, the return value of the
 listener will be used as the value of the reply.
 
-```js title='Main Process'
+```js title='Main Process' @ts-type={somePromise:(...args:unknown[])=>Promise<unknown>}
 ipcMain.handle('my-invokable-ipc', async (event, ...args) => {
   const result = await somePromise(...args)
   return result
 })
 ```
 
-```js title='Renderer Process'
+```js title='Renderer Process' @ts-type={arg1:unknown} @ts-type={arg2:unknown}
 async () => {
   const result = await ipcRenderer.invoke('my-invokable-ipc', arg1, arg2)
   // ...

+ 1 - 1
docs/api/ipc-renderer.md

@@ -101,7 +101,7 @@ The main process should listen for `channel` with
 
 For example:
 
-```javascript
+```javascript @ts-type={someArgument:unknown} @ts-type={doSomeWork:(arg:unknown)=>Promise<unknown>}
 // Renderer process
 ipcRenderer.invoke('some-name', someArgument).then((result) => {
   // ...

+ 3 - 3
docs/api/menu.md

@@ -147,7 +147,7 @@ can have a submenu.
 
 An example of creating the application menu with the simple template API:
 
-```javascript
+```javascript @ts-expect-error=[107]
 const { app, Menu } = require('electron')
 
 const isMac = process.platform === 'darwin'
@@ -267,7 +267,7 @@ menu on behalf of the renderer.
 
 Below is an example of showing a menu when the user right clicks the page:
 
-```js
+```js @ts-expect-error=[21]
 // renderer
 window.addEventListener('contextmenu', (e) => {
   e.preventDefault()
@@ -289,7 +289,7 @@ ipcMain.on('show-context-menu', (event) => {
     { label: 'Menu Item 2', type: 'checkbox', checked: true }
   ]
   const menu = Menu.buildFromTemplate(template)
-  menu.popup(BrowserWindow.fromWebContents(event.sender))
+  menu.popup({ window: BrowserWindow.fromWebContents(event.sender) })
 })
 ```
 

+ 4 - 3
docs/api/message-channel-main.md

@@ -17,7 +17,8 @@ Example:
 
 ```js
 // Main process
-const { MessageChannelMain } = require('electron')
+const { BrowserWindow, MessageChannelMain } = require('electron')
+const w = new BrowserWindow()
 const { port1, port2 } = new MessageChannelMain()
 w.webContents.postMessage('port', null, [port2])
 port1.postMessage({ some: 'message' })
@@ -26,9 +27,9 @@ port1.postMessage({ some: 'message' })
 const { ipcRenderer } = require('electron')
 ipcRenderer.on('port', (e) => {
   // e.ports is a list of ports sent along with this message
-  e.ports[0].on('message', (messageEvent) => {
+  e.ports[0].onmessage = (messageEvent) => {
     console.log(messageEvent.data)
-  })
+  }
 })
 ```
 

+ 1 - 1
docs/api/net-log.md

@@ -5,7 +5,7 @@
 Process: [Main](../glossary.md#main-process)
 
 ```javascript
-const { netLog } = require('electron')
+const { app, netLog } = require('electron')
 
 app.whenReady().then(async () => {
   await netLog.startLogging('/path/to/net-log')

+ 9 - 9
docs/api/protocol.md

@@ -32,7 +32,7 @@ To have your custom protocol work in combination with a custom session, you need
 to register it to that session explicitly.
 
 ```javascript
-const { session, app, protocol } = require('electron')
+const { app, BrowserWindow, net, protocol, session } = require('electron')
 const path = require('path')
 const url = require('url')
 
@@ -41,11 +41,11 @@ app.whenReady().then(() => {
   const ses = session.fromPartition(partition)
 
   ses.protocol.handle('atom', (request) => {
-    const path = request.url.slice('atom://'.length)
-    return net.fetch(url.pathToFileURL(path.join(__dirname, path)))
+    const filePath = request.url.slice('atom://'.length)
+    return net.fetch(url.pathToFileURL(path.join(__dirname, filePath)).toString())
   })
 
-  mainWindow = new BrowserWindow({ webPreferences: { partition } })
+  const mainWindow = new BrowserWindow({ webPreferences: { partition } })
 })
 ```
 
@@ -121,9 +121,9 @@ Either a `Response` or a `Promise<Response>` can be returned.
 Example:
 
 ```js
-import { app, protocol } from 'electron'
-import { join } from 'path'
-import { pathToFileURL } from 'url'
+const { app, net, protocol } = require('electron')
+const { join } = require('path')
+const { pathToFileURL } = require('url')
 
 protocol.registerSchemesAsPrivileged([
   {
@@ -131,7 +131,7 @@ protocol.registerSchemesAsPrivileged([
     privileges: {
       standard: true,
       secure: true,
-      supportsFetchAPI: true
+      supportFetchAPI: true
     }
   }
 ])
@@ -147,7 +147,7 @@ app.whenReady().then(() => {
       }
       // NB, this does not check for paths that escape the bundle, e.g.
       // app://bundle/../../secret_file.txt
-      return net.fetch(pathToFileURL(join(__dirname, pathname)))
+      return net.fetch(pathToFileURL(join(__dirname, pathname)).toString())
     } else if (host === 'api') {
       return net.fetch('https://api.my-server.com/' + pathname, {
         method: req.method,

+ 29 - 27
docs/api/session.md

@@ -98,7 +98,7 @@ Emitted when Electron is about to download `item` in `webContents`.
 Calling `event.preventDefault()` will cancel the download and `item` will not be
 available from next tick of the process.
 
-```javascript
+```javascript @ts-expect-error=[4]
 const { session } = require('electron')
 session.defaultSession.on('will-download', (event, item, webContents) => {
   event.preventDefault()
@@ -214,7 +214,7 @@ cancel the request.  Additionally, permissioning on `navigator.hid` can
 be further managed by using [`ses.setPermissionCheckHandler(handler)`](#sessetpermissioncheckhandlerhandler)
 and [`ses.setDevicePermissionHandler(handler)`](#sessetdevicepermissionhandlerhandler).
 
-```javascript
+```javascript @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -253,7 +253,7 @@ app.whenReady().then(() => {
   win.webContents.session.on('select-hid-device', (event, details, callback) => {
     event.preventDefault()
     const selectedDevice = details.deviceList.find((device) => {
-      return device.vendorId === '9025' && device.productId === '67'
+      return device.vendorId === 9025 && device.productId === 67
     })
     callback(selectedDevice?.deviceId)
   })
@@ -320,7 +320,7 @@ cancel the request.  Additionally, permissioning on `navigator.serial` can
 be managed by using [ses.setPermissionCheckHandler(handler)](#sessetpermissioncheckhandlerhandler)
 with the `serial` permission.
 
-```javascript
+```javascript @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -463,7 +463,7 @@ cancel the request.  Additionally, permissioning on `navigator.usb` can
 be further managed by using [`ses.setPermissionCheckHandler(handler)`](#sessetpermissioncheckhandlerhandler)
 and [`ses.setDevicePermissionHandler(handler)`](#sessetdevicepermissionhandlerhandler).
 
-```javascript
+```javascript @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)} @ts-type={updateGrantedDevices:(devices:Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)=>void}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -502,7 +502,7 @@ app.whenReady().then(() => {
   win.webContents.session.on('select-usb-device', (event, details, callback) => {
     event.preventDefault()
     const selectedDevice = details.deviceList.find((device) => {
-      return device.vendorId === '9025' && device.productId === '67'
+      return device.vendorId === 9025 && device.productId === 67
     })
     if (selectedDevice) {
       // Optionally, add this to the persisted devices (updateGrantedDevices needs to be implemented by developer to persist permissions)
@@ -755,15 +755,17 @@ Sets download saving directory. By default, the download directory will be the
 Emulates network with the given configuration for the `session`.
 
 ```javascript
+const win = new BrowserWindow()
+
 // To emulate a GPRS connection with 50kbps throughput and 500 ms latency.
-window.webContents.session.enableNetworkEmulation({
+win.webContents.session.enableNetworkEmulation({
   latency: 500,
   downloadThroughput: 6400,
   uploadThroughput: 6400
 })
 
 // To emulate a network outage.
-window.webContents.session.enableNetworkEmulation({ offline: true })
+win.webContents.session.enableNetworkEmulation({ offline: true })
 ```
 
 #### `ses.preconnect(options)`
@@ -1036,7 +1038,7 @@ Additionally, the default behavior of Electron is to store granted device permis
 If longer term storage is needed, a developer can store granted device
 permissions (eg when handling the `select-hid-device` event) and then read from that storage with `setDevicePermissionHandler`.
 
-```javascript
+```javascript @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -1084,9 +1086,9 @@ app.whenReady().then(() => {
   win.webContents.session.on('select-hid-device', (event, details, callback) => {
     event.preventDefault()
     const selectedDevice = details.deviceList.find((device) => {
-      return device.vendorId === '9025' && device.productId === '67'
+      return device.vendorId === 9025 && device.productId === 67
     })
-    callback(selectedPort?.deviceId)
+    callback(selectedDevice?.deviceId)
   })
 })
 ```
@@ -1174,31 +1176,31 @@ macOS does not require a handler because macOS handles the pairing
 automatically.  To clear the handler, call `setBluetoothPairingHandler(null)`.
 
 ```javascript
-
-const { app, BrowserWindow, ipcMain, session } = require('electron')
-
-let bluetoothPinCallback = null
+const { app, BrowserWindow, session } = require('electron')
+const path = require('path')
 
 function createWindow () {
+  let bluetoothPinCallback = null
+
   const mainWindow = new BrowserWindow({
     webPreferences: {
       preload: path.join(__dirname, 'preload.js')
     }
   })
-}
 
-// Listen for an IPC message from the renderer to get the response for the Bluetooth pairing.
-ipcMain.on('bluetooth-pairing-response', (event, response) => {
-  bluetoothPinCallback(response)
-})
+  mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
+    bluetoothPinCallback = callback
+    // Send a IPC message to the renderer to prompt the user to confirm the pairing.
+    // Note that this will require logic in the renderer to handle this message and
+    // display a prompt to the user.
+    mainWindow.webContents.send('bluetooth-pairing-request', details)
+  })
 
-mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
-  bluetoothPinCallback = callback
-  // Send a IPC message to the renderer to prompt the user to confirm the pairing.
-  // Note that this will require logic in the renderer to handle this message and
-  // display a prompt to the user.
-  mainWindow.webContents.send('bluetooth-pairing-request', details)
-})
+  // Listen for an IPC message from the renderer to get the response for the Bluetooth pairing.
+  mainWindow.webContents.ipc.on('bluetooth-pairing-response', (event, response) => {
+    bluetoothPinCallback(response)
+  })
+}
 
 app.whenReady().then(() => {
   createWindow()

+ 4 - 4
docs/api/touch-bar.md

@@ -87,12 +87,12 @@ const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar
 let spinning = false
 
 // Reel labels
-const reel1 = new TouchBarLabel()
-const reel2 = new TouchBarLabel()
-const reel3 = new TouchBarLabel()
+const reel1 = new TouchBarLabel({ label: '' })
+const reel2 = new TouchBarLabel({ label: '' })
+const reel3 = new TouchBarLabel({ label: '' })
 
 // Spin result label
-const result = new TouchBarLabel()
+const result = new TouchBarLabel({ label: '' })
 
 // Spin button
 const spin = new TouchBarButton({

+ 29 - 18
docs/api/web-contents.md

@@ -98,7 +98,7 @@ async function lookupTargetId (browserWindow) {
   await wc.debugger.attach('1.3')
   const { targetInfo } = await wc.debugger.sendCommand('Target.getTargetInfo')
   const { targetId } = targetInfo
-  const targetWebContents = await webContents.fromDevToolsTargetId(targetId)
+  const targetWebContents = await wc.fromDevToolsTargetId(targetId)
 }
 ```
 
@@ -1020,9 +1020,9 @@ e.g. the `http://` or `file://`. If the load should bypass http cache then
 use the `pragma` header to achieve it.
 
 ```javascript
-const { webContents } = require('electron')
+const win = new BrowserWindow()
 const options = { extraHeaders: 'pragma: no-cache\n' }
-webContents.loadURL('https://github.com', options)
+win.webContents.loadURL('https://github.com', options)
 ```
 
 #### `contents.loadFile(filePath[, options])`
@@ -1052,6 +1052,7 @@ an app structure like this:
 Would require code like this
 
 ```js
+const win = new BrowserWindow()
 win.loadFile('src/index.html')
 ```
 
@@ -1188,7 +1189,9 @@ when this process is unstable or unusable, for instance in order to recover
 from the `unresponsive` event.
 
 ```js
-contents.on('unresponsive', async () => {
+const win = new BrowserWindow()
+
+win.webContents.on('unresponsive', async () => {
   const { response } = await dialog.showMessageBox({
     message: 'App X has become unresponsive',
     title: 'Do you want to try forcefully reloading the app?',
@@ -1196,8 +1199,8 @@ contents.on('unresponsive', async () => {
     cancelId: 1
   })
   if (response === 0) {
-    contents.forcefullyCrashRenderer()
-    contents.reload()
+    win.webContents.forcefullyCrashRenderer()
+    win.webContents.reload()
   }
 })
 ```
@@ -1224,8 +1227,9 @@ Injects CSS into the current web page and returns a unique key for the inserted
 stylesheet.
 
 ```js
-contents.on('did-finish-load', () => {
-  contents.insertCSS('html, body { background-color: #f00; }')
+const win = new BrowserWindow()
+win.webContents.on('did-finish-load', () => {
+  win.webContents.insertCSS('html, body { background-color: #f00; }')
 })
 ```
 
@@ -1239,9 +1243,11 @@ Removes the inserted CSS from the current web page. The stylesheet is identified
 by its key, which is returned from `contents.insertCSS(css)`.
 
 ```js
-contents.on('did-finish-load', async () => {
-  const key = await contents.insertCSS('html, body { background-color: #f00; }')
-  contents.removeInsertedCSS(key)
+const win = new BrowserWindow()
+
+win.webContents.on('did-finish-load', async () => {
+  const key = await win.webContents.insertCSS('html, body { background-color: #f00; }')
+  win.webContents.removeInsertedCSS(key)
 })
 ```
 
@@ -1262,7 +1268,9 @@ this limitation.
 Code execution will be suspended until web page stop loading.
 
 ```js
-contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true)
+const win = new BrowserWindow()
+
+win.webContents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true)
   .then((result) => {
     console.log(result) // Will be the JSON object from the fetch call
   })
@@ -1373,7 +1381,8 @@ Sets the maximum and minimum pinch-to-zoom level.
 > **NOTE**: Visual zoom is disabled by default in Electron. To re-enable it, call:
 >
 > ```js
-> contents.setVisualZoomLevelLimits(1, 3)
+> const win = new BrowserWindow()
+> win.webContents.setVisualZoomLevelLimits(1, 3)
 > ```
 
 #### `contents.undo()`
@@ -1508,12 +1517,12 @@ can be obtained by subscribing to [`found-in-page`](web-contents.md#event-found-
 Stops any `findInPage` request for the `webContents` with the provided `action`.
 
 ```javascript
-const { webContents } = require('electron')
-webContents.on('found-in-page', (event, result) => {
-  if (result.finalUpdate) webContents.stopFindInPage('clearSelection')
+const win = new BrowserWindow()
+win.webContents.on('found-in-page', (event, result) => {
+  if (result.finalUpdate) win.webContents.stopFindInPage('clearSelection')
 })
 
-const requestId = webContents.findInPage('api')
+const requestId = win.webContents.findInPage('api')
 console.log(requestId)
 ```
 
@@ -1593,6 +1602,7 @@ Use `page-break-before: always;` CSS style to force to print to a new page.
 Example usage:
 
 ```js
+const win = new BrowserWindow()
 const options = {
   silent: true,
   deviceName: 'My-Printer',
@@ -1889,8 +1899,9 @@ For example:
 
 ```js
 // Main process
+const win = new BrowserWindow()
 const { port1, port2 } = new MessageChannelMain()
-webContents.postMessage('port', { message: 'hello' }, [port1])
+win.webContents.postMessage('port', { message: 'hello' }, [port1])
 
 // Renderer process
 ipcRenderer.on('port', (e, msg) => {

+ 2 - 1
docs/api/web-frame-main.md

@@ -128,8 +128,9 @@ For example:
 
 ```js
 // Main process
+const win = new BrowserWindow()
 const { port1, port2 } = new MessageChannelMain()
-webContents.mainFrame.postMessage('port', { message: 'hello' }, [port1])
+win.webContents.mainFrame.postMessage('port', { message: 'hello' }, [port1])
 
 // Renderer process
 ipcRenderer.on('port', (e, msg) => {

+ 1 - 2
docs/api/web-frame.md

@@ -96,13 +96,12 @@ with an array of misspelt words when complete.
 
 An example of using [node-spellchecker][spellchecker] as provider:
 
-```javascript
+```javascript @ts-expect-error=[2,6]
 const { webFrame } = require('electron')
 const spellChecker = require('spellchecker')
 webFrame.setSpellCheckProvider('en-US', {
   spellCheck (words, callback) {
     setTimeout(() => {
-      const spellchecker = require('spellchecker')
       const misspelled = words.filter(x => spellchecker.isMisspelled(x))
       callback(misspelled)
     }, 0)

+ 5 - 5
docs/api/webview-tag.md

@@ -255,7 +255,7 @@ The `webview` tag has the following methods:
 
 **Example**
 
-```javascript
+```javascript @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('dom-ready', () => {
   webview.openDevTools()
@@ -799,7 +799,7 @@ Fired when the guest window logs a console message.
 The following example code forwards all log messages to the embedder's console
 without regard for log level or other properties.
 
-```javascript
+```javascript @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('console-message', (e) => {
   console.log('Guest page logged a message:', e.message)
@@ -820,7 +820,7 @@ Returns:
 Fired when a result is available for
 [`webview.findInPage`](#webviewfindinpagetext-options) request.
 
-```javascript
+```javascript @ts-expect-error=[3,6]
 const webview = document.querySelector('webview')
 webview.addEventListener('found-in-page', (e) => {
   webview.stopFindInPage('keepSelection')
@@ -945,7 +945,7 @@ Fired when the guest page attempts to close itself.
 The following example code navigates the `webview` to `about:blank` when the
 guest attempts to close itself.
 
-```javascript
+```javascript @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('close', () => {
   webview.src = 'about:blank'
@@ -965,7 +965,7 @@ Fired when the guest page has sent an asynchronous message to embedder page.
 With `sendToHost` method and `ipc-message` event you can communicate
 between guest page and embedder page:
 
-```javascript
+```javascript @ts-expect-error=[4,7]
 // In embedder page.
 const webview = document.querySelector('webview')
 webview.addEventListener('ipc-message', (event) => {

+ 1 - 1
docs/tutorial/asar-archives.md

@@ -55,7 +55,7 @@ fs.readdirSync('/path/to/example.asar')
 
 Use a module from the archive:
 
-```javascript
+```javascript @ts-nocheck
 require('./path/to/example.asar/dir/module.js')
 ```
 

+ 1 - 1
docs/tutorial/asar-integrity.md

@@ -40,7 +40,7 @@ Valid `algorithm` values are currently `SHA256` only.  The `hash` is a hash of t
 
 ASAR integrity checking is currently disabled by default and can be enabled by toggling a fuse. See [Electron Fuses](fuses.md) for more information on what Electron Fuses are and how they work.  When enabling this fuse you typically also want to enable the `onlyLoadAppFromAsar` fuse otherwise the validity checking can be bypassed via the Electron app code search path.
 
-```js
+```js @ts-nocheck
 require('@electron/fuses').flipFuses(
   // E.g. /a/b/Foo.app
   pathToPackagedApp,

+ 8 - 8
docs/tutorial/automated-testing.md

@@ -90,7 +90,7 @@ Usage of `selenium-webdriver` with Electron is the same as with
 normal websites, except that you have to manually specify how to connect
 ChromeDriver and where to find the binary of your Electron app:
 
-```js title='test.js'
+```js title='test.js' @ts-expect-error=[1]
 const webdriver = require('selenium-webdriver')
 const driver = new webdriver.Builder()
   // The "9515" is the port opened by ChromeDriver.
@@ -155,7 +155,7 @@ Playwright launches your app in development mode through the `_electron.launch`
 To point this API to your Electron app, you can pass the path to your main process
 entry point (here, it is `main.js`).
 
-```js {5}
+```js {5} @ts-nocheck
 const { _electron: electron } = require('playwright')
 const { test } = require('@playwright/test')
 
@@ -169,7 +169,7 @@ test('launch app', async () => {
 After that, you will access to an instance of Playwright's `ElectronApp` class. This
 is a powerful class that has access to main process modules for example:
 
-```js {6-11}
+```js {6-11} @ts-nocheck
 const { _electron: electron } = require('playwright')
 const { test } = require('@playwright/test')
 
@@ -189,7 +189,7 @@ test('get isPackaged', async () => {
 It can also create individual [Page][playwright-page] objects from Electron BrowserWindow instances.
 For example, to grab the first BrowserWindow and save a screenshot:
 
-```js {6-7}
+```js {6-7} @ts-nocheck
 const { _electron: electron } = require('playwright')
 const { test } = require('@playwright/test')
 
@@ -205,7 +205,7 @@ test('save screenshot', async () => {
 Putting all this together using the PlayWright Test runner, let's create a `example.spec.js`
 test file with a single test and assertion:
 
-```js title='example.spec.js'
+```js title='example.spec.js' @ts-nocheck
 const { _electron: electron } = require('playwright')
 const { test, expect } = require('@playwright/test')
 
@@ -259,7 +259,7 @@ expose custom methods to your test suite.
 To create a custom driver, we'll use Node.js' [`child_process`](https://nodejs.org/api/child_process.html) API.
 The test suite will spawn the Electron process, then establish a simple messaging protocol:
 
-```js title='testDriver.js'
+```js title='testDriver.js' @ts-nocheck
 const childProcess = require('child_process')
 const electronPath = require('electron')
 
@@ -296,7 +296,7 @@ For convenience, you may want to wrap `appProcess` in a driver object that provi
 high-level functions. Here is an example of how you can do this. Let's start by creating
 a `TestDriver` class:
 
-```js title='testDriver.js'
+```js title='testDriver.js' @ts-nocheck
 class TestDriver {
   constructor ({ path, args, env }) {
     this.rpcCalls = []
@@ -378,7 +378,7 @@ framework of your choosing. The following example uses
 [`ava`](https://www.npmjs.com/package/ava), but other popular choices like Jest
 or Mocha would work as well:
 
-```js title='test.js'
+```js title='test.js' @ts-nocheck
 const test = require('ava')
 const electronPath = require('electron')
 const { TestDriver } = require('./testDriver')

+ 3 - 3
docs/tutorial/code-signing.md

@@ -67,7 +67,7 @@ are likely using [`electron-packager`][], which includes [`@electron/osx-sign`][
 If you're using Packager's API, you can pass [in configuration that both signs
 and notarizes your application](https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html).
 
-```js
+```js @ts-nocheck
 const packager = require('electron-packager')
 
 packager({
@@ -116,7 +116,7 @@ Electron app. This is the tool used under the hood by Electron Forge's
 `electron-winstaller` directly, use the `certificateFile` and `certificatePassword` configuration
 options when creating your installer.
 
-```js {10-11}
+```js {10-11} @ts-nocheck
 const electronInstaller = require('electron-winstaller')
 // NB: Use this syntax within an async function, Node does not have support for
 //     top-level await as of Node 12.
@@ -146,7 +146,7 @@ If you're not using Electron Forge and want to use `electron-wix-msi` directly,
 `certificateFile` and `certificatePassword` configuration options
 or pass in parameters directly to [SignTool.exe][] with the `signWithParams` option.
 
-```js {12-13}
+```js {12-13} @ts-nocheck
 import { MSICreator } from 'electron-wix-msi'
 
 // Step 1: Instantiate the MSICreator

+ 4 - 4
docs/tutorial/context-isolation.md

@@ -16,7 +16,7 @@ Context isolation has been enabled by default since Electron 12, and it is a rec
 
 Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. With context isolation disabled, your preload script would share a common global `window` object with the renderer. You could then attach arbitrary properties to a preload script:
 
-```javascript title='preload.js'
+```javascript title='preload.js' @ts-nocheck
 // preload with contextIsolation disabled
 window.myAPI = {
   doAThing: () => {}
@@ -25,7 +25,7 @@ window.myAPI = {
 
 The `doAThing()` function could then be used directly in the renderer process:
 
-```javascript title='renderer.js'
+```javascript title='renderer.js' @ts-nocheck
 // use the exposed API in the renderer
 window.myAPI.doAThing()
 ```
@@ -43,7 +43,7 @@ contextBridge.exposeInMainWorld('myAPI', {
 })
 ```
 
-```javascript title='renderer.js'
+```javascript title='renderer.js' @ts-nocheck
 // use the exposed API in the renderer
 window.myAPI.doAThing()
 ```
@@ -98,7 +98,7 @@ declare global {
 
 Doing so will ensure that the TypeScript compiler will know about the `electronAPI` property on your global `window` object when writing scripts in your renderer process:
 
-```typescript title='renderer.ts'
+```typescript title='renderer.ts' @ts-nocheck
 window.electronAPI.loadPreferences()
 ```
 

+ 1 - 1
docs/tutorial/dark-mode.md

@@ -116,7 +116,7 @@ Now the renderer process can communicate with the main process securely and perf
 
 The `renderer.js` file is responsible for controlling the `<button>` functionality.
 
-```js title='renderer.js'
+```js title='renderer.js' @ts-expect-error=[2,7]
 document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
   const isDarkMode = await window.darkMode.toggle()
   document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'

+ 1 - 1
docs/tutorial/fuses.md

@@ -67,7 +67,7 @@ The loadBrowserProcessSpecificV8Snapshot fuse changes which V8 snapshot file is
 
 We've made a handy module, [`@electron/fuses`](https://npmjs.com/package/@electron/fuses), to make flipping these fuses easy.  Check out the README of that module for more details on usage and potential error cases.
 
-```js
+```js @ts-nocheck
 require('@electron/fuses').flipFuses(
   // Path to electron
   require('electron'),

+ 1 - 1
docs/tutorial/installation.md

@@ -66,7 +66,7 @@ You can use environment variables to override the base URL, the path at which to
 look for Electron binaries, and the binary filename. The URL used by `@electron/get`
 is composed as follows:
 
-```javascript
+```javascript @ts-nocheck
 url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME
 ```
 

+ 8 - 8
docs/tutorial/ipc.md

@@ -138,7 +138,7 @@ To make these elements interactive, we'll be adding a few lines of code in the i
 `renderer.js` file that leverages the `window.electronAPI` functionality exposed from the preload
 script:
 
-```javascript title='renderer.js (Renderer Process)'
+```javascript title='renderer.js (Renderer Process)' @ts-expect-error=[4,5]
 const setButton = document.getElementById('btn')
 const titleInput = document.getElementById('title')
 setButton.addEventListener('click', () => {
@@ -182,13 +182,13 @@ provided to the renderer process. Please refer to
 :::
 
 ```javascript {6-13,25} title='main.js (Main Process)'
-const { BrowserWindow, dialog, ipcMain } = require('electron')
+const { app, BrowserWindow, dialog, ipcMain } = require('electron')
 const path = require('path')
 
 // ...
 
 async function handleFileOpen () {
-  const { canceled, filePaths } = await dialog.showOpenDialog()
+  const { canceled, filePaths } = await dialog.showOpenDialog({})
   if (!canceled) {
     return filePaths[0]
   }
@@ -203,7 +203,7 @@ function createWindow () {
   mainWindow.loadFile('index.html')
 }
 
-app.whenReady(() => {
+app.whenReady().then(() => {
   ipcMain.handle('dialog:openFile', handleFileOpen)
   createWindow()
 })
@@ -263,7 +263,7 @@ The UI consists of a single `#btn` button element that will be used to trigger o
 a `#filePath` element that will be used to display the path of the selected file. Making these
 pieces work will take a few lines of code in the renderer process script:
 
-```javascript title='renderer.js (Renderer Process)'
+```javascript title='renderer.js (Renderer Process)' @ts-expect-error=[5]
 const btn = document.getElementById('btn')
 const filePathElement = document.getElementById('filePath')
 
@@ -412,7 +412,7 @@ function createWindow () {
 For the purposes of the tutorial, it's important to note that the `click` handler
 sends a message (either `1` or `-1`) to the renderer process through the `update-counter` channel.
 
-```javascript
+```javascript @ts-nocheck
 click: () => mainWindow.webContents.send('update-counter', -1)
 ```
 
@@ -486,7 +486,7 @@ To tie it all together, we'll create an interface in the loaded HTML file that c
 Finally, to make the values update in the HTML document, we'll add a few lines of DOM manipulation
 so that the value of the `#counter` element is updated whenever we fire an `update-counter` event.
 
-```javascript title='renderer.js (Renderer Process)'
+```javascript title='renderer.js (Renderer Process)' @ts-nocheck
 const counter = document.getElementById('counter')
 
 window.electronAPI.onUpdateCounter((_event, value) => {
@@ -509,7 +509,7 @@ We can demonstrate this with slight modifications to the code from the previous
 renderer process, use the `event` parameter to send a reply back to the main process through the
 `counter-value` channel.
 
-```javascript title='renderer.js (Renderer Process)'
+```javascript title='renderer.js (Renderer Process)' @ts-nocheck
 const counter = document.getElementById('counter')
 
 window.electronAPI.onUpdateCounter((event, value) => {

+ 2 - 2
docs/tutorial/keyboard-shortcuts.md

@@ -56,7 +56,7 @@ Starting with a working application from the
 [Quick Start Guide](quick-start.md), update the `main.js` file with the
 following lines:
 
-```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/global'
+```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/global' @ts-type={createWindow:()=>void}
 const { app, globalShortcut } = require('electron')
 
 app.whenReady().then(() => {
@@ -131,7 +131,7 @@ If you don't want to do manual shortcut parsing, there are libraries that do
 advanced key detection, such as [mousetrap][]. Below are examples of usage of the
 `mousetrap` running in the Renderer process:
 
-```js
+```js @ts-nocheck
 Mousetrap.bind('4', () => { console.log('4') })
 Mousetrap.bind('?', () => { console.log('show shortcuts!') })
 Mousetrap.bind('esc', () => { console.log('escape') }, 'keyup')

+ 5 - 3
docs/tutorial/launch-app-from-url-in-another-app.md

@@ -45,6 +45,8 @@ if (process.defaultApp) {
 We will now define the function in charge of creating our browser window and load our application's `index.html` file.
 
 ```javascript
+let mainWindow
+
 const createWindow = () => {
   // Create the browser window.
   mainWindow = new BrowserWindow({
@@ -65,7 +67,7 @@ This code will be different in Windows compared to MacOS and Linux. This is due
 
 #### Windows code:
 
-```javascript
+```javascript @ts-type={mainWindow:Electron.BrowserWindow} @ts-type={createWindow:()=>void}
 const gotTheLock = app.requestSingleInstanceLock()
 
 if (!gotTheLock) {
@@ -91,7 +93,7 @@ if (!gotTheLock) {
 
 #### MacOS and Linux code:
 
-```javascript
+```javascript @ts-type={createWindow:()=>void}
 // This method will be called when Electron has finished
 // initialization and is ready to create browser windows.
 // Some APIs can only be used after this event occurs.
@@ -166,7 +168,7 @@ If you're using Electron Packager's API, adding support for protocol handlers is
 Electron Forge is handled, except
 `protocols` is part of the Packager options passed to the `packager` function.
 
-```javascript
+```javascript @ts-nocheck
 const packager = require('electron-packager')
 
 packager({

+ 4 - 4
docs/tutorial/message-ports.md

@@ -126,7 +126,7 @@ app.whenReady().then(async () => {
 Then, in your preload scripts you receive the port through IPC and set up the
 listeners.
 
-```js title='preloadMain.js and preloadSecondary.js (Preload scripts)'
+```js title='preloadMain.js and preloadSecondary.js (Preload scripts)' @ts-nocheck
 const { ipcRenderer } = require('electron')
 
 ipcRenderer.on('port', e => {
@@ -148,7 +148,7 @@ That means window.electronMessagePort is globally available and you can call
 `postMessage` on it from anywhere in your app to send a message to the other
 renderer.
 
-```js title='renderer.js (Renderer Process)'
+```js title='renderer.js (Renderer Process)' @ts-nocheck
 // elsewhere in your code to send a message to the other renderers message handler
 window.electronMessagePort.postmessage('ping')
 ```
@@ -181,7 +181,7 @@ app.whenReady().then(async () => {
   // We can't use ipcMain.handle() here, because the reply needs to transfer a
   // MessagePort.
   // Listen for message sent from the top-level frame
-  mainWindow.webContents.mainFrame.on('request-worker-channel', (event) => {
+  mainWindow.webContents.mainFrame.ipc.on('request-worker-channel', (event) => {
     // Create a new channel ...
     const { port1, port2 } = new MessageChannelMain()
     // ... send one end to the worker ...
@@ -245,7 +245,7 @@ Electron's built-in IPC methods only support two modes: fire-and-forget
 can implement a "response stream", where a single request responds with a
 stream of data.
 
-```js title='renderer.js (Renderer Process)'
+```js title='renderer.js (Renderer Process)' @ts-expect-error=[18]
 const makeStreamingRequest = (element, callback) => {
   // MessageChannels are lightweight--it's cheap to create a new one for each
   // request.

+ 1 - 1
docs/tutorial/multithreading.md

@@ -42,7 +42,7 @@ safe.
 The only way to load a native module safely for now, is to make sure the app
 loads no native modules after the Web Workers get started.
 
-```javascript
+```javascript @ts-expect-error=[1]
 process.dlopen = () => {
   throw new Error('Load native module is not safe')
 }

+ 2 - 1
docs/tutorial/native-file-drag-drop.md

@@ -22,6 +22,7 @@ In `preload.js` use the [`contextBridge`][] to inject a method `window.electron.
 
 ```js
 const { contextBridge, ipcRenderer } = require('electron')
+const path = require('path')
 
 contextBridge.exposeInMainWorld('electron', {
   startDrag: (fileName) => {
@@ -43,7 +44,7 @@ Add a draggable element to `index.html`, and reference your renderer script:
 
 In `renderer.js` set up the renderer process to handle drag events by calling the method you added via the [`contextBridge`][] above.
 
-```javascript
+```javascript @ts-expect-error=[3]
 document.getElementById('drag').ondragstart = (event) => {
   event.preventDefault()
   window.electron.startDrag('drag-and-drop.md')

+ 3 - 3
docs/tutorial/performance.md

@@ -173,7 +173,7 @@ in the fictitious `.foo` format. In order to do that, it relies on the
 equally fictitious `foo-parser` module. In traditional Node.js development,
 you might write code that eagerly loads dependencies:
 
-```js title='parser.js'
+```js title='parser.js' @ts-expect-error=[2]
 const fs = require('fs')
 const fooParser = require('foo-parser')
 
@@ -196,7 +196,7 @@ In the above example, we're doing a lot of work that's being executed as soon
 as the file is loaded. Do we need to get parsed files right away? Could we
 do this work a little later, when `getParsedFiles()` is actually called?
 
-```js title='parser.js'
+```js title='parser.js' @ts-expect-error=[20]
 // "fs" is likely already being loaded, so the `require()` call is cheap
 const fs = require('fs')
 
@@ -205,7 +205,7 @@ class Parser {
     // Touch the disk as soon as `getFiles` is called, not sooner.
     // Also, ensure that we're not blocking other operations by using
     // the asynchronous version.
-    this.files = this.files || await fs.readdir('.')
+    this.files = this.files || await fs.promises.readdir('.')
 
     return this.files
   }

+ 3 - 3
docs/tutorial/process-model.md

@@ -175,13 +175,13 @@ Although preload scripts share a `window` global with the renderer they're attac
 you cannot directly attach any variables from the preload script to `window` because of
 the [`contextIsolation`][context-isolation] default.
 
-```js title='preload.js'
+```js title='preload.js' @ts-nocheck
 window.myAPI = {
   desktop: true
 }
 ```
 
-```js title='renderer.js'
+```js title='renderer.js' @ts-nocheck
 console.log(window.myAPI)
 // => undefined
 ```
@@ -200,7 +200,7 @@ contextBridge.exposeInMainWorld('myAPI', {
 })
 ```
 
-```js title='renderer.js'
+```js title='renderer.js' @ts-nocheck
 console.log(window.myAPI)
 // => { desktop: true }
 ```

+ 3 - 2
docs/tutorial/quick-start.md

@@ -182,7 +182,7 @@ In Electron, browser windows can only be created after the `app` module's
 [`app.whenReady()`][app-when-ready] API. Call `createWindow()` after `whenReady()`
 resolves its Promise.
 
-```js
+```js @ts-type={createWindow:()=>void}
 app.whenReady().then(() => {
   createWindow()
 })
@@ -239,7 +239,7 @@ from within your existing `whenReady()` callback.
 
 [activate]: ../api/app.md#event-activate-macos
 
-```js
+```js @ts-type={createWindow:()=>void}
 app.whenReady().then(() => {
   createWindow()
 
@@ -290,6 +290,7 @@ To attach this script to your renderer process, pass in the path to your preload
 to the `webPreferences.preload` option in your existing `BrowserWindow` constructor.
 
 ```js
+const { app, BrowserWindow } = require('electron')
 // include the Node.js 'path' module at the top of your file
 const path = require('path')
 

+ 8 - 7
docs/tutorial/security.md

@@ -141,7 +141,7 @@ like `HTTP`. Similarly, we recommend the use of `WSS` over `WS`, `FTPS` over
 
 #### How?
 
-```js title='main.js (Main Process)'
+```js title='main.js (Main Process)' @ts-type={browserWindow:Electron.BrowserWindow}
 // Bad
 browserWindow.loadURL('http://example.com')
 
@@ -278,7 +278,7 @@ security-conscious developers might want to assume the very opposite.
 
 ```js title='main.js (Main Process)'
 const { session } = require('electron')
-const URL = require('url').URL
+const { URL } = require('url')
 
 session
   .fromPartition('some-partition')
@@ -608,7 +608,8 @@ sometimes be fooled - a `startsWith('https://example.com')` test would let
 `https://example.com.attacker.com` through.
 
 ```js title='main.js (Main Process)'
-const URL = require('url').URL
+const { URL } = require('url')
+const { app } = require('electron')
 
 app.on('web-contents-created', (event, contents) => {
   contents.on('will-navigate', (event, navigationUrl) => {
@@ -647,8 +648,8 @@ receive, amongst other parameters, the `url` the window was requested to open
 and the options used to create it. We recommend that you register a handler to
 monitor the creation of windows, and deny any unexpected window creation.
 
-```js title='main.js (Main Process)'
-const { shell } = require('electron')
+```js title='main.js (Main Process)' @ts-type={isSafeForExternalOpen:(url:string)=>boolean}
+const { app, shell } = require('electron')
 
 app.on('web-contents-created', (event, contents) => {
   contents.setWindowOpenHandler(({ url }) => {
@@ -683,7 +684,7 @@ leveraged to execute arbitrary commands.
 
 #### How?
 
-```js title='main.js (Main Process)'
+```js title='main.js (Main Process)' @ts-type={USER_CONTROLLED_DATA_HERE:string}
 //  Bad
 const { shell } = require('electron')
 shell.openExternal(USER_CONTROLLED_DATA_HERE)
@@ -739,7 +740,7 @@ You should be validating the `sender` of **all** IPC messages by default.
 
 #### How?
 
-```js title='main.js (Main Process)'
+```js title='main.js (Main Process)' @ts-type={getSecrets:()=>unknown}
 // Bad
 ipcMain.handle('get-secrets', () => {
   return getSecrets()

+ 1 - 1
docs/tutorial/snapcraft.md

@@ -72,7 +72,7 @@ npx electron-installer-snap --src=out/myappname-linux-x64
 If you have an existing build pipeline, you can use `electron-installer-snap`
 programmatically. For more information, see the [Snapcraft API docs][snapcraft-syntax].
 
-```js
+```js @ts-nocheck
 const snap = require('electron-installer-snap')
 
 snap(options)

+ 8 - 8
docs/tutorial/spellchecker.md

@@ -20,12 +20,12 @@ On macOS as we use the native APIs there is no way to set the language that the
 
 For Windows and Linux there are a few Electron APIs you should use to set the languages for the spellchecker.
 
-```js
+```js @ts-type={myWindow:Electron.BrowserWindow}
 // Sets the spellchecker to check English US and French
-myWindow.session.setSpellCheckerLanguages(['en-US', 'fr'])
+myWindow.webContents.session.setSpellCheckerLanguages(['en-US', 'fr'])
 
 // An array of all available language codes
-const possibleLanguages = myWindow.session.availableSpellCheckerLanguages
+const possibleLanguages = myWindow.webContents.session.availableSpellCheckerLanguages
 ```
 
 By default the spellchecker will enable the language matching the current OS locale.
@@ -35,7 +35,7 @@ By default the spellchecker will enable the language matching the current OS loc
 All the required information to generate a context menu is provided in the [`context-menu`](../api/web-contents.md#event-context-menu) event on each `webContents` instance.  A small example
 of how to make a context menu with this information is provided below.
 
-```js
+```js @ts-type={myWindow:Electron.BrowserWindow}
 const { Menu, MenuItem } = require('electron')
 
 myWindow.webContents.on('context-menu', (event, params) => {
@@ -45,7 +45,7 @@ myWindow.webContents.on('context-menu', (event, params) => {
   for (const suggestion of params.dictionarySuggestions) {
     menu.append(new MenuItem({
       label: suggestion,
-      click: () => mainWindow.webContents.replaceMisspelling(suggestion)
+      click: () => myWindow.webContents.replaceMisspelling(suggestion)
     }))
   }
 
@@ -54,7 +54,7 @@ myWindow.webContents.on('context-menu', (event, params) => {
     menu.append(
       new MenuItem({
         label: 'Add to dictionary',
-        click: () => mainWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
+        click: () => myWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
       })
     )
   }
@@ -67,8 +67,8 @@ myWindow.webContents.on('context-menu', (event, params) => {
 
 Although the spellchecker itself does not send any typings, words or user input to Google services the hunspell dictionary files are downloaded from a Google CDN by default.  If you want to avoid this you can provide an alternative URL to download the dictionaries from.
 
-```js
-myWindow.session.setSpellCheckerDictionaryDownloadURL('https://example.com/dictionaries/')
+```js @ts-type={myWindow:Electron.BrowserWindow}
+myWindow.webContents.session.setSpellCheckerDictionaryDownloadURL('https://example.com/dictionaries/')
 ```
 
 Check out the docs for [`session.setSpellCheckerDictionaryDownloadURL`](../api/session.md#sessetspellcheckerdictionarydownloadurlurl) for more information on where to get the dictionary files from and how you need to host them.

+ 2 - 2
docs/tutorial/tray.md

@@ -51,7 +51,7 @@ app.whenReady().then(() => {
 
 Great! Now we can start attaching a context menu to our Tray, like so:
 
-```js
+```js @ts-expect-error=[8]
 const contextMenu = Menu.buildFromTemplate([
   { label: 'Item1', type: 'radio' },
   { label: 'Item2', type: 'radio' },
@@ -68,7 +68,7 @@ To read more about constructing native menus, click
 
 Finally, let's give our tray a tooltip and a title.
 
-```js
+```js @ts-type={tray:Electron.Tray}
 tray.setToolTip('This is my application')
 tray.setTitle('This is my title')
 ```

+ 2 - 2
docs/tutorial/tutorial-2-first-app.md

@@ -256,7 +256,7 @@ const createWindow = () => {
 
 ### Calling your function when the app is ready
 
-```js title='main.js (Lines 12-14)'
+```js title='main.js (Lines 12-14)' @ts-type={createWindow:()=>void}
 app.whenReady().then(() => {
   createWindow()
 })
@@ -336,7 +336,7 @@ Because windows cannot be created before the `ready` event, you should only list
 `activate` events after your app is initialized. Do this by only listening for activate
 events inside your existing `whenReady()` callback.
 
-```js
+```js @ts-type={createWindow:()=>void}
 app.whenReady().then(() => {
   createWindow()
 

+ 2 - 2
docs/tutorial/tutorial-3-preload.md

@@ -118,7 +118,7 @@ information in the window. This variable can be accessed via `window.versions` o
 `versions`. Create a `renderer.js` script that uses the [`document.getElementById`][]
 DOM API to replace the displayed text for the HTML element with `info` as its `id` property.
 
-```js title="renderer.js"
+```js title="renderer.js" @ts-nocheck
 const information = document.getElementById('info')
 information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
 ```
@@ -225,7 +225,7 @@ app.whenReady().then(() => {
 Once you have the sender and receiver set up, you can now send messages from the renderer
 to the main process through the `'ping'` channel you just defined.
 
-```js title='renderer.js'
+```js title='renderer.js' @ts-expect-error=[2]
 const func = async () => {
   const response = await window.versions.ping()
   console.log(response) // prints out 'pong'

+ 1 - 1
docs/tutorial/tutorial-6-publishing-updating.md

@@ -188,7 +188,7 @@ npm install update-electron-app
 
 Then, import the module and call it immediately in the main process.
 
-```js title='main.js'
+```js title='main.js' @ts-nocheck
 require('update-electron-app')()
 ```
 

+ 2 - 2
docs/tutorial/updates.md

@@ -32,7 +32,7 @@ npm install update-electron-app
 
 Then, invoke the updater from your app's main process file:
 
-```js title="main.js"
+```js title="main.js" @ts-nocheck
 require('update-electron-app')()
 ```
 
@@ -113,7 +113,7 @@ Now that you've configured the basic update mechanism for your application, you
 need to ensure that the user will get notified when there's an update. This
 can be achieved using the [autoUpdater API events](../api/auto-updater.md#events):
 
-```javascript title="main.js"
+```javascript title="main.js" @ts-expect-error=[11]
 autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
   const dialogOpts = {
     type: 'info',

+ 2 - 2
docs/tutorial/window-customization.md

@@ -196,9 +196,9 @@ const win = new BrowserWindow({
   }
 })
 
-ipcMain.on('set-ignore-mouse-events', (event, ...args) => {
+ipcMain.on('set-ignore-mouse-events', (event, ignore, options) => {
   const win = BrowserWindow.fromWebContents(event.sender)
-  win.setIgnoreMouseEvents(...args)
+  win.setIgnoreMouseEvents(ignore, options)
 })
 ```
 

+ 5 - 5
docs/tutorial/windows-taskbar.md

@@ -125,7 +125,7 @@ Starting with a working application from the
 following lines:
 
 ```javascript
-const { BrowserWindow } = require('electron')
+const { BrowserWindow, nativeImage } = require('electron')
 const path = require('path')
 
 const win = new BrowserWindow()
@@ -133,11 +133,11 @@ const win = new BrowserWindow()
 win.setThumbarButtons([
   {
     tooltip: 'button1',
-    icon: path.join(__dirname, 'button1.png'),
+    icon: nativeImage.createFromPath(path.join(__dirname, 'button1.png')),
     click () { console.log('button1 clicked') }
   }, {
     tooltip: 'button2',
-    icon: path.join(__dirname, 'button2.png'),
+    icon: nativeImage.createFromPath(path.join(__dirname, 'button2.png')),
     flags: ['enabled', 'dismissonclick'],
     click () { console.log('button2 clicked.') }
   }
@@ -189,11 +189,11 @@ Starting with a working application from the
 following lines:
 
 ```javascript
-const { BrowserWindow } = require('electron')
+const { BrowserWindow, nativeImage } = require('electron')
 
 const win = new BrowserWindow()
 
-win.setOverlayIcon('path/to/overlay.png', 'Description for overlay')
+win.setOverlayIcon(nativeImage.createFromPath('path/to/overlay.png'), 'Description for overlay')
 ```
 
 [msdn-icon-overlay]: https://learn.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#icon-overlays

+ 4 - 2
package.json

@@ -9,7 +9,7 @@
     "@electron/docs-parser": "^1.1.0",
     "@electron/fiddle-core": "^1.0.4",
     "@electron/github-app-auth": "^2.0.0",
-    "@electron/lint-roller": "^1.2.1",
+    "@electron/lint-roller": "^1.5.0",
     "@electron/typescript-definitions": "^8.14.0",
     "@octokit/rest": "^19.0.7",
     "@primer/octicons": "^10.0.0",
@@ -30,6 +30,7 @@
     "@types/stream-json": "^1.5.1",
     "@types/temp": "^0.8.34",
     "@types/uuid": "^3.4.6",
+    "@types/w3c-web-serial": "^1.0.3",
     "@types/webpack": "^5.28.0",
     "@types/webpack-env": "^1.17.0",
     "@typescript-eslint/eslint-plugin": "^5.59.7",
@@ -87,10 +88,11 @@
     "lint:objc": "node ./script/lint.js --objc",
     "lint:py": "node ./script/lint.js --py",
     "lint:gn": "node ./script/lint.js --gn",
-    "lint:docs": "remark docs -qf && npm run lint:js-in-markdown && npm run create-typescript-definitions && npm run lint:docs-fiddles && npm run lint:docs-relative-links && npm run lint:markdownlint",
+    "lint:docs": "remark docs -qf && npm run lint:js-in-markdown && npm run create-typescript-definitions && npm run lint:ts-check-js-in-markdown && npm run lint:docs-fiddles && npm run lint:docs-relative-links && npm run lint:markdownlint",
     "lint:docs-fiddles": "standard \"docs/fiddles/**/*.js\"",
     "lint:docs-relative-links": "electron-lint-markdown-links --root docs \"**/*.md\"",
     "lint:markdownlint": "electron-markdownlint \"*.md\" \"docs/**/*.md\"",
+    "lint:ts-check-js-in-markdown": "electron-lint-markdown-ts-check --root docs \"**/*.md\" --ignore \"breaking-changes.md\"",
     "lint:js-in-markdown": "electron-lint-markdown-standard --root docs \"**/*.md\"",
     "create-api-json": "node script/create-api-json.js",
     "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --api=electron-api.json && node spec/ts-smoke/runner.js",

+ 53 - 5
yarn.lock

@@ -194,10 +194,10 @@
     "@octokit/auth-app" "^4.0.13"
     "@octokit/rest" "^19.0.11"
 
-"@electron/lint-roller@^1.2.1":
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.2.1.tgz#9f9d99b0a8975646e0a0131ab1b21a2ec62e80d8"
-  integrity sha512-w9PelpTBX8ClAv2iVa8fYqK77dnN0zWiHW98Utf8D83nmxkCMQNrKdRupSyiuIttbic1Nao8FhTScppmzOz0gw==
+"@electron/lint-roller@^1.5.0":
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.5.0.tgz#9b743979e1b03327e475fa696bb781eb2ea05ef2"
+  integrity sha512-205UxwJEx8zv5wLwPq4wMA0OYrJ7d1GuqOhPav0Uy2HWe4K+DZbSP50safCvZCSpI6Op3DMo79tp5i8VppuPWA==
   dependencies:
     "@dsanders11/vscode-markdown-languageservice" "^0.3.0"
     glob "^8.1.0"
@@ -206,6 +206,7 @@
     mdast-util-from-markdown "^1.3.0"
     minimist "^1.2.8"
     node-fetch "^2.6.9"
+    rimraf "^4.4.1"
     standard "^17.0.0"
     unist-util-visit "^4.1.2"
     vscode-languageserver "^8.1.0"
@@ -1066,6 +1067,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/w3c-web-serial@^1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@types/w3c-web-serial/-/w3c-web-serial-1.0.3.tgz#9fd5e8542f74e464bb1715b384b5c0dcbf2fb2c3"
+  integrity sha512-R4J/OjqKAUFQoXVIkaUTfzb/sl6hLh/ZhDTfowJTRMa7LhgEmI/jXV4zsL1u8HpNa853BxwNmDIr0pauizzwSQ==
+
 "@types/webpack-env@^1.17.0":
   version "1.17.0"
   resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0"
@@ -3156,6 +3162,16 @@ glob@^8.1.0:
     minimatch "^5.0.1"
     once "^1.3.0"
 
+glob@^9.2.0:
+  version "9.3.5"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21"
+  integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==
+  dependencies:
+    fs.realpath "^1.0.0"
+    minimatch "^8.0.2"
+    minipass "^4.2.4"
+    path-scurry "^1.6.1"
+
 glob@~8.0.3:
   version "8.0.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e"
@@ -4078,7 +4094,7 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
-lru-cache@^9.0.0:
+lru-cache@^9.0.0, lru-cache@^9.1.1:
   version "9.1.1"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
   integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==
@@ -4514,6 +4530,13 @@ minimatch@^5.0.1:
   dependencies:
     brace-expansion "^2.0.1"
 
+minimatch@^8.0.2:
+  version "8.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229"
+  integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==
+  dependencies:
+    brace-expansion "^2.0.1"
+
 minimatch@~5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.2.tgz#0939d7d6f0898acbd1508abe534d1929368a8fff"
@@ -4543,6 +4566,16 @@ minipass@^4.0.0:
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.1.tgz#2b9408c6e81bb8b338d600fb3685e375a370a057"
   integrity sha512-V9esFpNbK0arbN3fm2sxDKqMYgIp7XtVdE4Esj+PE4Qaaxdg1wIw48ITQIOn1sc8xXSmUviVL3cyjMqPlrVkiA==
 
+minipass@^4.2.4:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a"
+  integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==
+
+"minipass@^5.0.0 || ^6.0.2":
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81"
+  integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==
+
 minizlib@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -4933,6 +4966,14 @@ path-parse@^1.0.6, path-parse@^1.0.7:
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
+path-scurry@^1.6.1:
+  version "1.9.2"
+  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.2.tgz#90f9d296ac5e37e608028e28a447b11d385b3f63"
+  integrity sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==
+  dependencies:
+    lru-cache "^9.1.1"
+    minipass "^5.0.0 || ^6.0.2"
+
 [email protected]:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -5830,6 +5871,13 @@ rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
+rimraf@^4.4.1:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755"
+  integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==
+  dependencies:
+    glob "^9.2.0"
+
 rimraf@~2.2.6:
   version "2.2.8"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"