Browse Source

chore: extend linting of code blocks in the docs (#40636)

David Sanders 1 year ago
parent
commit
e45e20acd5
71 changed files with 839 additions and 454 deletions
  1. 1 1
      docs/api/accelerator.md
  2. 1 1
      docs/api/browser-view.md
  3. 12 12
      docs/api/browser-window.md
  4. 3 3
      docs/api/client-request.md
  5. 1 1
      docs/api/clipboard.md
  6. 2 2
      docs/api/command-line-switches.md
  7. 1 1
      docs/api/command-line.md
  8. 1 1
      docs/api/content-tracing.md
  9. 6 6
      docs/api/context-bridge.md
  10. 1 1
      docs/api/cookies.md
  11. 1 1
      docs/api/crash-reporter.md
  12. 1 1
      docs/api/debugger.md
  13. 3 3
      docs/api/desktop-capturer.md
  14. 3 3
      docs/api/dialog.md
  15. 1 1
      docs/api/dock.md
  16. 1 1
      docs/api/download-item.md
  17. 1 1
      docs/api/environment-variables.md
  18. 1 1
      docs/api/global-shortcut.md
  19. 1 1
      docs/api/incoming-message.md
  20. 1 1
      docs/api/ipc-renderer.md
  21. 4 4
      docs/api/menu.md
  22. 4 4
      docs/api/native-image.md
  23. 1 1
      docs/api/net-log.md
  24. 1 1
      docs/api/net.md
  25. 1 1
      docs/api/power-save-blocker.md
  26. 6 6
      docs/api/protocol.md
  27. 1 1
      docs/api/push-notifications.md
  28. 16 7
      docs/api/screen.md
  29. 1 1
      docs/api/service-workers.md
  30. 18 18
      docs/api/session.md
  31. 1 1
      docs/api/shell.md
  32. 1 1
      docs/api/structures/printer-info.md
  33. 3 3
      docs/api/system-preferences.md
  34. 1 1
      docs/api/touch-bar.md
  35. 2 2
      docs/api/tray.md
  36. 12 12
      docs/api/web-contents.md
  37. 2 2
      docs/api/web-frame-main.md
  38. 4 4
      docs/api/web-frame.md
  39. 1 1
      docs/api/web-request.md
  40. 6 6
      docs/api/webview-tag.md
  41. 2 2
      docs/api/window-open.md
  42. 4 4
      docs/breaking-changes.md
  43. 1 1
      docs/development/creating-api.md
  44. 4 4
      docs/faq.md
  45. 1 1
      docs/tutorial/application-debugging.md
  46. 6 6
      docs/tutorial/asar-archives.md
  47. 9 9
      docs/tutorial/context-isolation.md
  48. 1 1
      docs/tutorial/dark-mode.md
  49. 5 5
      docs/tutorial/devices.md
  50. 1 1
      docs/tutorial/devtools-extension.md
  51. 0 9
      docs/tutorial/examples.md
  52. 1 1
      docs/tutorial/in-app-purchases.md
  53. 1 1
      docs/tutorial/installation.md
  54. 18 18
      docs/tutorial/ipc.md
  55. 54 12
      docs/tutorial/keyboard-shortcuts.md
  56. 7 7
      docs/tutorial/launch-app-from-url-in-another-app.md
  57. 3 3
      docs/tutorial/macos-dock.md
  58. 2 2
      docs/tutorial/multithreading.md
  59. 45 5
      docs/tutorial/native-file-drag-drop.md
  60. 33 12
      docs/tutorial/notifications.md
  61. 28 6
      docs/tutorial/offscreen-rendering.md
  62. 8 5
      docs/tutorial/progress-bar.md
  63. 4 4
      docs/tutorial/recent-documents.md
  64. 14 12
      docs/tutorial/represented-file.md
  65. 1 1
      docs/tutorial/security.md
  66. 5 5
      docs/tutorial/updates.md
  67. 12 12
      docs/tutorial/window-customization.md
  68. 6 6
      docs/tutorial/windows-taskbar.md
  69. 2 2
      package.json
  70. 429 174
      script/lint.js
  71. 3 3
      yarn.lock

+ 1 - 1
docs/api/accelerator.md

@@ -15,7 +15,7 @@ Shortcuts are registered with the [`globalShortcut`](global-shortcut.md) module
 using the [`register`](global-shortcut.md#globalshortcutregisteraccelerator-callback)
 method, i.e.
 
-```javascript
+```js
 const { app, globalShortcut } = require('electron')
 
 app.whenReady().then(() => {

+ 1 - 1
docs/api/browser-view.md

@@ -16,7 +16,7 @@ module is emitted.
 
 ### Example
 
-```javascript
+```js
 // In the main process.
 const { app, BrowserView, BrowserWindow } = require('electron')
 

+ 12 - 12
docs/api/browser-window.md

@@ -7,7 +7,7 @@ Process: [Main](../glossary.md#main-process)
 This module cannot be used until the `ready` event of the `app`
 module is emitted.
 
-```javascript
+```js
 // In the main process.
 const { BrowserWindow } = require('electron')
 
@@ -38,7 +38,7 @@ While loading the page, the `ready-to-show` event will be emitted when the rende
 process has rendered the page for the first time if the window has not been shown yet. Showing
 the window after this event will have no visual flash:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ show: false })
 win.once('ready-to-show', () => {
@@ -59,7 +59,7 @@ For a complex app, the `ready-to-show` event could be emitted too late, making
 the app feel slow. In this case, it is recommended to show the window
 immediately, and use a `backgroundColor` close to your app's background:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow({ backgroundColor: '#2e2c29' })
@@ -85,7 +85,7 @@ For more information about these color types see valid options in [win.setBackgr
 
 By using `parent` option, you can create child windows:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const top = new BrowserWindow()
@@ -101,7 +101,7 @@ The `child` window will always show on top of the `top` window.
 A modal window is a child window that disables parent window, to create a modal
 window, you have to set both `parent` and `modal` options:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const top = new BrowserWindow()
@@ -188,7 +188,7 @@ window should be closed, which will also be called when the window is
 reloaded. In Electron, returning any value other than `undefined` would cancel the
 close. For example:
 
-```javascript
+```js
 window.onbeforeunload = (e) => {
   console.log('I do not want to be closed')
 
@@ -351,7 +351,7 @@ Commands are lowercased, underscores are replaced with hyphens, and the
 `APPCOMMAND_` prefix is stripped off.
 e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 win.on('app-command', (e, cmd) => {
@@ -456,7 +456,7 @@ Returns `BrowserWindow | null` - The window with the given `id`.
 
 Objects created with `new BrowserWindow` have the following properties:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 // In this example `win` is our instance
 const win = new BrowserWindow({ width: 800, height: 600 })
@@ -780,7 +780,7 @@ Closes the currently open [Quick Look][quick-look] panel.
 
 Resizes and moves the window to the supplied bounds. Any properties that are not supplied will default to their current values.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 
@@ -1035,7 +1035,7 @@ Changes the attachment point for sheets on macOS. By default, sheets are
 attached just below the window frame, but you may want to display them beneath
 a HTML-rendered toolbar. For example:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 
@@ -1178,7 +1178,7 @@ To ensure that file URLs are properly formatted, it is recommended to use
 Node's [`url.format`](https://nodejs.org/api/url.html#url_url_format_urlobject)
 method:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 
@@ -1194,7 +1194,7 @@ win.loadURL(url)
 You can load a URL using a `POST` request with URL-encoded data by doing
 the following:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 

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

@@ -65,7 +65,7 @@ strictly follow the Node.js model as described in the
 
 For instance, we could have created the same request to 'github.com' as follows:
 
-```javascript
+```js
 const request = net.request({
   method: 'GET',
   protocol: 'https:',
@@ -104,7 +104,7 @@ The `callback` function is expected to be called back with user credentials:
 * `username` string
 * `password` string
 
-```javascript @ts-type={request:Electron.ClientRequest}
+```js @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 @ts-type={request:Electron.ClientRequest}
+```js @ts-type={request:Electron.ClientRequest}
 request.on('response', (response) => {
   console.log(`STATUS: ${response.statusCode}`)
   response.on('error', (error) => {

+ 1 - 1
docs/api/clipboard.md

@@ -7,7 +7,7 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer
 On Linux, there is also a `selection` clipboard. To manipulate it
 you need to pass `selection` to each method:
 
-```javascript
+```js
 const { clipboard } = require('electron')
 
 clipboard.writeText('Example string', 'selection')

+ 2 - 2
docs/api/command-line-switches.md

@@ -6,7 +6,7 @@ You can use [app.commandLine.appendSwitch][append-switch] to append them in
 your app's main script before the [ready][ready] event of the [app][app] module
 is emitted:
 
-```javascript
+```js
 const { app } = require('electron')
 app.commandLine.appendSwitch('remote-debugging-port', '8315')
 app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1')
@@ -185,7 +185,7 @@ list of hosts. This flag has an effect only if used in tandem with
 
 For example:
 
-```javascript
+```js
 const { app } = require('electron')
 app.commandLine.appendSwitch('proxy-bypass-list', '<local>;*.google.com;*foo.com;1.2.3.4:5678')
 ```

+ 1 - 1
docs/api/command-line.md

@@ -7,7 +7,7 @@ _This class is not exported from the `'electron'` module. It is only available a
 
 The following example shows how to check if the `--disable-gpu` flag is set.
 
-```javascript
+```js
 const { app } = require('electron')
 app.commandLine.hasSwitch('disable-gpu')
 ```

+ 1 - 1
docs/api/content-tracing.md

@@ -10,7 +10,7 @@ This module does not include a web interface. To view recorded traces, use
 **Note:** You should not use this module until the `ready` event of the app
 module is emitted.
 
-```javascript
+```js
 const { app, contentTracing } = require('electron')
 
 app.whenReady().then(() => {

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

@@ -6,7 +6,7 @@ Process: [Renderer](../glossary.md#renderer-process)
 
 An example of exposing an API to a renderer from an isolated preload script is given below:
 
-```javascript
+```js
 // Preload (Isolated World)
 const { contextBridge, ipcRenderer } = require('electron')
 
@@ -18,7 +18,7 @@ contextBridge.exposeInMainWorld(
 )
 ```
 
-```javascript @ts-nocheck
+```js @ts-nocheck
 // Renderer (Main World)
 
 window.electron.doThing()
@@ -64,7 +64,7 @@ the API become immutable and updates on either side of the bridge do not result
 
 An example of a complex API is shown below:
 
-```javascript
+```js
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInMainWorld(
@@ -92,7 +92,7 @@ contextBridge.exposeInMainWorld(
 
 An example of `exposeInIsolatedWorld` is shown below:
 
-```javascript
+```js
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInIsolatedWorld(
@@ -104,7 +104,7 @@ contextBridge.exposeInIsolatedWorld(
 )
 ```
 
-```javascript @ts-nocheck
+```js @ts-nocheck
 // Renderer (In isolated world id1004)
 
 window.electron.doThing()
@@ -145,7 +145,7 @@ The table of supported types described above also applies to Node APIs that you
 Please note that many Node APIs grant access to local system resources.
 Be very cautious about which globals and APIs you expose to untrusted remote content.
 
-```javascript
+```js
 const { contextBridge } = require('electron')
 const crypto = require('node:crypto')
 contextBridge.exposeInMainWorld('nodeCrypto', {

+ 1 - 1
docs/api/cookies.md

@@ -10,7 +10,7 @@ a `Session`.
 
 For example:
 
-```javascript
+```js
 const { session } = require('electron')
 
 // Query all cookies.

+ 1 - 1
docs/api/crash-reporter.md

@@ -7,7 +7,7 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer
 The following is an example of setting up Electron to automatically submit
 crash reports to a remote server:
 
-```javascript
+```js
 const { crashReporter } = require('electron')
 
 crashReporter.start({ submitURL: 'https://your-domain.com/url-to-submit' })

+ 1 - 1
docs/api/debugger.md

@@ -8,7 +8,7 @@ _This class is not exported from the `'electron'` module. It is only available a
 Chrome Developer Tools has a [special binding][rdp] available at JavaScript
 runtime that allows interacting with pages and instrumenting them.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 

+ 3 - 3
docs/api/desktop-capturer.md

@@ -8,7 +8,7 @@ Process: [Main](../glossary.md#main-process)
 The following example shows how to capture video from a desktop window whose
 title is `Electron`:
 
-```javascript
+```js
 // In the main process.
 const { BrowserWindow, desktopCapturer } = require('electron')
 
@@ -24,7 +24,7 @@ desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources =
 })
 ```
 
-```javascript @ts-nocheck
+```js @ts-nocheck
 // In the preload script.
 const { ipcRenderer } = require('electron')
 
@@ -68,7 +68,7 @@ To capture both audio and video from the entire desktop the constraints passed
 to [`navigator.mediaDevices.getUserMedia`][] must include `chromeMediaSource: 'desktop'`,
 for both `audio` and `video`, but should not include a `chromeMediaSourceId` constraint.
 
-```javascript
+```js
 const constraints = {
   audio: {
     mandatory: {

+ 3 - 3
docs/api/dialog.md

@@ -6,7 +6,7 @@ Process: [Main](../glossary.md#main-process)
 
 An example of showing a dialog to select multiple files:
 
-```javascript
+```js
 const { dialog } = require('electron')
 console.log(dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))
 ```
@@ -52,7 +52,7 @@ The `browserWindow` argument allows the dialog to attach itself to a parent wind
 The `filters` specifies an array of file types that can be displayed or
 selected when you want to limit the user to a specific type. For example:
 
-```javascript
+```js
 {
   filters: [
     { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
@@ -119,7 +119,7 @@ The `browserWindow` argument allows the dialog to attach itself to a parent wind
 The `filters` specifies an array of file types that can be displayed or
 selected when you want to limit the user to a specific type. For example:
 
-```javascript
+```js
 {
   filters: [
     { name: 'Images', extensions: ['jpg', 'png', 'gif'] },

+ 1 - 1
docs/api/dock.md

@@ -7,7 +7,7 @@ _This class is not exported from the `'electron'` module. It is only available a
 
 The following example shows how to bounce your icon on the dock.
 
-```javascript
+```js
 const { app } = require('electron')
 app.dock.bounce()
 ```

+ 1 - 1
docs/api/download-item.md

@@ -9,7 +9,7 @@ _This class is not exported from the `'electron'` module. It is only available a
 It is used in `will-download` event of `Session` class, and allows users to
 control the download item.
 
-```javascript
+```js
 // In the main process.
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()

+ 1 - 1
docs/api/environment-variables.md

@@ -59,7 +59,7 @@ geolocation webservice. To enable this feature, acquire a
 and place the following code in your main process file, before opening any
 browser windows that will make geolocation requests:
 
-```javascript
+```js
 process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE'
 ```
 

+ 1 - 1
docs/api/global-shortcut.md

@@ -12,7 +12,7 @@ shortcuts.
 not have the keyboard focus. This module cannot be used before the `ready`
 event of the app module is emitted.
 
-```javascript
+```js
 const { app, globalShortcut } = require('electron')
 
 app.whenReady().then(() => {

+ 1 - 1
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 @ts-type={response:Electron.IncomingMessage}
+```js @ts-type={response:Electron.IncomingMessage}
 // Prints something like:
 //
 // [ 'user-agent',

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

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

+ 4 - 4
docs/api/menu.md

@@ -151,7 +151,7 @@ can have a submenu.
 
 An example of creating the application menu with the simple template API:
 
-```javascript @ts-expect-error=[107]
+```js @ts-expect-error=[107]
 const { app, Menu } = require('electron')
 
 const isMac = process.platform === 'darwin'
@@ -353,7 +353,7 @@ By default, items will be inserted in the order they exist in the template unles
 
 Template:
 
-```javascript
+```js
 [
   { id: '1', label: 'one' },
   { id: '2', label: 'two' },
@@ -373,7 +373,7 @@ Menu:
 
 Template:
 
-```javascript
+```js
 [
   { id: '1', label: 'one' },
   { type: 'separator' },
@@ -397,7 +397,7 @@ Menu:
 
 Template:
 
-```javascript
+```js
 [
   { id: '1', label: 'one', after: ['3'] },
   { id: '2', label: 'two', before: ['1'] },

+ 4 - 4
docs/api/native-image.md

@@ -10,7 +10,7 @@ In Electron, for the APIs that take images, you can pass either file paths or
 For example, when creating a tray or setting a window's icon, you can pass an
 image file path as a `string`:
 
-```javascript
+```js
 const { BrowserWindow, Tray } = require('electron')
 
 const appIcon = new Tray('/Users/somebody/images/icon.png')
@@ -20,7 +20,7 @@ console.log(appIcon, win)
 
 Or read the image from the clipboard, which returns a `NativeImage`:
 
-```javascript
+```js
 const { clipboard, Tray } = require('electron')
 const image = clipboard.readImage()
 const appIcon = new Tray(image)
@@ -71,7 +71,7 @@ images/
 └── [email protected]
 ```
 
-```javascript
+```js
 const { Tray } = require('electron')
 const appIcon = new Tray('/Users/somebody/images/icon.png')
 console.log(appIcon)
@@ -138,7 +138,7 @@ Creates a new `NativeImage` instance from a file located at `path`. This method
 returns an empty image if the `path` does not exist, cannot be read, or is not
 a valid image.
 
-```javascript
+```js
 const nativeImage = require('electron').nativeImage
 
 const image = nativeImage.createFromPath('/Users/somebody/images/icon.png')

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

@@ -4,7 +4,7 @@
 
 Process: [Main](../glossary.md#main-process)
 
-```javascript
+```js
 const { app, netLog } = require('electron')
 
 app.whenReady().then(async () => {

+ 1 - 1
docs/api/net.md

@@ -26,7 +26,7 @@ Node.js.
 
 Example usage:
 
-```javascript
+```js
 const { app } = require('electron')
 app.whenReady().then(() => {
   const { net } = require('electron')

+ 1 - 1
docs/api/power-save-blocker.md

@@ -6,7 +6,7 @@ Process: [Main](../glossary.md#main-process)
 
 For example:
 
-```javascript
+```js
 const { powerSaveBlocker } = require('electron')
 
 const id = powerSaveBlocker.start('prevent-display-sleep')

+ 6 - 6
docs/api/protocol.md

@@ -7,7 +7,7 @@ Process: [Main](../glossary.md#main-process)
 An example of implementing a protocol that has the same effect as the
 `file://` protocol:
 
-```javascript
+```js
 const { app, protocol, net } = require('electron')
 
 app.whenReady().then(() => {
@@ -31,7 +31,7 @@ a different session and your custom protocol will not work if you just use
 To have your custom protocol work in combination with a custom session, you need
 to register it to that session explicitly.
 
-```javascript
+```js
 const { app, BrowserWindow, net, protocol, session } = require('electron')
 const path = require('node:path')
 const url = require('url')
@@ -67,7 +67,7 @@ video/audio. Specify a privilege with the value of `true` to enable the capabili
 An example of registering a privileged scheme, that bypasses Content Security
 Policy:
 
-```javascript
+```js
 const { protocol } = require('electron')
 protocol.registerSchemesAsPrivileged([
   { scheme: 'foo', privileges: { bypassCSP: true } }
@@ -212,7 +212,7 @@ property.
 
 Example:
 
-```javascript
+```js
 protocol.registerBufferProtocol('atom', (request, callback) => {
   callback({ mimeType: 'text/html', data: Buffer.from('<h5>Response</h5>') })
 })
@@ -267,7 +267,7 @@ has the `data` property.
 
 Example:
 
-```javascript
+```js
 const { protocol } = require('electron')
 const { PassThrough } = require('stream')
 
@@ -292,7 +292,7 @@ protocol.registerStreamProtocol('atom', (request, callback) => {
 It is possible to pass any object that implements the readable stream API (emits
 `data`/`end`/`error` events). For example, here's how a file could be returned:
 
-```javascript
+```js
 protocol.registerStreamProtocol('atom', (request, callback) => {
   callback(fs.createReadStream('index.html'))
 })

+ 1 - 1
docs/api/push-notifications.md

@@ -6,7 +6,7 @@ Process: [Main](../glossary.md#main-process)
 
 For example, when registering for push notifications via Apple push notification services (APNS):
 
-```javascript
+```js
 const { pushNotifications, Notification } = require('electron')
 
 pushNotifications.registerForAPNSNotifications().then((token) => {

+ 16 - 7
docs/api/screen.md

@@ -14,20 +14,29 @@ property, so writing `let { screen } = require('electron')` will not work.
 
 An example of creating a window that fills the whole screen:
 
-```javascript fiddle='docs/fiddles/screen/fit-screen'
-const { app, BrowserWindow, screen } = require('electron')
+```fiddle docs/fiddles/screen/fit-screen
+// Retrieve information about screen size, displays, cursor position, etc.
+//
+// For more info, see:
+// https://www.electronjs.org/docs/latest/api/screen
+
+const { app, BrowserWindow, screen } = require('electron/main')
+
+let mainWindow = null
 
-let win
 app.whenReady().then(() => {
-  const { width, height } = screen.getPrimaryDisplay().workAreaSize
-  win = new BrowserWindow({ width, height })
-  win.loadURL('https://github.com')
+  // Create a window that fills the screen's available work area.
+  const primaryDisplay = screen.getPrimaryDisplay()
+  const { width, height } = primaryDisplay.workAreaSize
+
+  mainWindow = new BrowserWindow({ width, height })
+  mainWindow.loadURL('https://electronjs.org')
 })
 ```
 
 Another example of creating a window in the external display:
 
-```javascript
+```js
 const { app, BrowserWindow, screen } = require('electron')
 
 let win

+ 1 - 1
docs/api/service-workers.md

@@ -10,7 +10,7 @@ a `Session`.
 
 For example:
 
-```javascript
+```js
 const { session } = require('electron')
 
 // Get all service workers.

+ 18 - 18
docs/api/session.md

@@ -9,7 +9,7 @@ The `session` module can be used to create new `Session` objects.
 You can also access the `session` of existing pages by using the `session`
 property of [`WebContents`](web-contents.md), or from the `session` module.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow({ width: 800, height: 600 })
@@ -75,7 +75,7 @@ _This class is not exported from the `'electron'` module. It is only available a
 
 You can create a `Session` object in the `session` module:
 
-```javascript
+```js
 const { session } = require('electron')
 const ses = session.fromPartition('persist:name')
 console.log(ses.getUserAgent())
@@ -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 @ts-expect-error=[4]
+```js @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 @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
+```js @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -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 @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
+```js @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 @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)} @ts-type={updateGrantedDevices:(devices:Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)=>void}
+```js @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)} @ts-type={updateGrantedDevices:(devices:Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)=>void}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -754,7 +754,7 @@ Sets download saving directory. By default, the download directory will be the
 
 Emulates network with the given configuration for the `session`.
 
-```javascript
+```js
 const win = new BrowserWindow()
 
 // To emulate a GPRS connection with 50kbps throughput and 500 ms latency.
@@ -868,7 +868,7 @@ calling `callback(-2)` rejects it.
 Calling `setCertificateVerifyProc(null)` will revert back to default certificate
 verify proc.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 
@@ -921,7 +921,7 @@ To clear the handler, call `setPermissionRequestHandler(null)`.  Please note tha
 you must also implement `setPermissionCheckHandler` to get complete permission handling.
 Most web APIs do a permission check and then make a permission request if the check is denied.
 
-```javascript
+```js
 const { session } = require('electron')
 session.fromPartition('some-partition').setPermissionRequestHandler((webContents, permission, callback) => {
   if (webContents.getURL() === 'some-host' && permission === 'notifications') {
@@ -967,7 +967,7 @@ you must also implement `setPermissionRequestHandler` to get complete permission
 Most web APIs do a permission check and then make a permission request if the check is denied.
 To clear the handler, call `setPermissionCheckHandler(null)`.
 
-```javascript
+```js
 const { session } = require('electron')
 const url = require('url')
 session.fromPartition('some-partition').setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
@@ -1012,7 +1012,7 @@ via the `navigator.mediaDevices.getDisplayMedia` API. Use the
 [desktopCapturer](desktop-capturer.md) API to choose which stream(s) to grant
 access to.
 
-```javascript
+```js
 const { session, desktopCapturer } = require('electron')
 
 session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
@@ -1026,7 +1026,7 @@ session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
 Passing a [WebFrameMain](web-frame-main.md) object as a video or audio stream
 will capture the video or audio stream from that frame.
 
-```javascript
+```js
 const { session } = require('electron')
 
 session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
@@ -1055,7 +1055,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 @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
+```js @ts-type={fetchGrantedDevices:()=>(Array<Electron.DevicePermissionHandlerHandlerDetails['device']>)}
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -1137,7 +1137,7 @@ The return value for the handler is a string array of USB classes which should b
 Returning an empty string array from the handler will allow all USB classes; returning the passed in array will maintain the default list of protected USB classes (this is also the default behavior if a handler is not defined).
 To clear the handler, call `setUSBProtectedClassesHandler(null)`.
 
-```javascript
+```js
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -1192,7 +1192,7 @@ that requires additional validation will be automatically cancelled.
 macOS does not require a handler because macOS handles the pairing
 automatically.  To clear the handler, call `setBluetoothPairingHandler(null)`.
 
-```javascript
+```js
 const { app, BrowserWindow, session } = require('electron')
 const path = require('node:path')
 
@@ -1238,7 +1238,7 @@ Clears the host resolver cache.
 Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate
 authentication.
 
-```javascript
+```js
 const { session } = require('electron')
 // consider any url ending with `example.com`, `foobar.com`, `baz`
 // for integrated authentication.
@@ -1543,7 +1543,7 @@ A [`WebRequest`](web-request.md) object for this session.
 
 A [`Protocol`](protocol.md) object for this session.
 
-```javascript
+```js
 const { app, session } = require('electron')
 const path = require('node:path')
 
@@ -1562,7 +1562,7 @@ app.whenReady().then(() => {
 
 A [`NetLog`](net-log.md) object for this session.
 
-```javascript
+```js
 const { app, session } = require('electron')
 
 app.whenReady().then(async () => {

+ 1 - 1
docs/api/shell.md

@@ -8,7 +8,7 @@ The `shell` module provides functions related to desktop integration.
 
 An example of opening a URL in the user's default browser:
 
-```javascript
+```js
 const { shell } = require('electron')
 
 shell.openExternal('https://github.com')

+ 1 - 1
docs/api/structures/printer-info.md

@@ -14,7 +14,7 @@ The number represented by `status` means different things on different platforms
 Below is an example of some of the additional options that may be set which
 may be different on each platform.
 
-```javascript
+```js
 {
   name: 'Austin_4th_Floor_Printer___C02XK13BJHD4',
   displayName: 'Austin 4th Floor Printer @ C02XK13BJHD4',

+ 3 - 3
docs/api/system-preferences.md

@@ -4,7 +4,7 @@
 
 Process: [Main](../glossary.md#main-process)
 
-```javascript
+```js
 const { systemPreferences } = require('electron')
 console.log(systemPreferences.isAeroGlassEnabled())
 ```
@@ -189,7 +189,7 @@ enabled, and `false` otherwise.
 An example of using it to determine if you should create a transparent window or
 not (transparent windows won't work correctly when DWM composition is disabled):
 
-```javascript
+```js
 const { BrowserWindow, systemPreferences } = require('electron')
 const browserOptions = { width: 1000, height: 800 }
 
@@ -348,7 +348,7 @@ Returns `boolean` - whether or not this device has the ability to use Touch ID.
 
 Returns `Promise<void>` - resolves if the user has successfully authenticated with Touch ID.
 
-```javascript
+```js
 const { systemPreferences } = require('electron')
 
 systemPreferences.promptTouchID('To get consent for a Security-Gated Thing').then(success => {

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

@@ -79,7 +79,7 @@ immediately updates the escape item in the touch bar.
 Below is an example of a simple slot machine touch bar game with a button
 and some labels.
 
-```javascript
+```js
 const { app, BrowserWindow, TouchBar } = require('electron')
 
 const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar

+ 2 - 2
docs/api/tray.md

@@ -8,7 +8,7 @@ Process: [Main](../glossary.md#main-process)
 
 `Tray` is an [EventEmitter][event-emitter].
 
-```javascript
+```js
 const { app, Menu, Tray } = require('electron')
 
 let tray = null
@@ -39,7 +39,7 @@ app.whenReady().then(() => {
 * In order for changes made to individual `MenuItem`s to take effect,
   you have to call `setContextMenu` again. For example:
 
-```javascript
+```js
 const { app, Menu, Tray } = require('electron')
 
 let appIcon = null

+ 12 - 12
docs/api/web-contents.md

@@ -9,7 +9,7 @@ It is responsible for rendering and controlling a web page and is a property of
 the [`BrowserWindow`](browser-window.md) object. An example of accessing the
 `webContents` object:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow({ width: 800, height: 1500 })
@@ -53,7 +53,7 @@ If you want to also observe navigations in `<iframe>`s, use [`will-frame-navigat
 
 These methods can be accessed from the `webContents` module:
 
-```javascript
+```js
 const { webContents } = require('electron')
 console.log(webContents)
 ```
@@ -439,7 +439,7 @@ Emitted when a `beforeunload` event handler is attempting to cancel a page unloa
 Calling `event.preventDefault()` will ignore the `beforeunload` event handler
 and allow the page to be unloaded.
 
-```javascript
+```js
 const { BrowserWindow, dialog } = require('electron')
 const win = new BrowserWindow({ width: 800, height: 600 })
 win.webContents.on('will-prevent-unload', (event) => {
@@ -541,7 +541,7 @@ and the menu shortcuts.
 To only prevent the menu shortcuts, use
 [`setIgnoreMenuShortcuts`](#contentssetignoremenushortcutsignore):
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow({ width: 800, height: 600 })
@@ -851,7 +851,7 @@ Due to the nature of bluetooth, scanning for devices when
 `select-bluetooth-device` to fire multiple times until `callback` is called
 with either a device id or an empty string to cancel the request.
 
-```javascript title='main.js'
+```js title='main.js'
 const { app, BrowserWindow } = require('electron')
 
 let win = null
@@ -886,7 +886,7 @@ Returns:
 Emitted when a new frame is generated. Only the dirty area is passed in the
 buffer.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow({ webPreferences: { offscreen: true } })
@@ -1018,7 +1018,7 @@ Loads the `url` in the window. The `url` must contain the protocol prefix,
 e.g. the `http://` or `file://`. If the load should bypass http cache then
 use the `pragma` header to achieve it.
 
-```javascript
+```js
 const win = new BrowserWindow()
 const options = { extraHeaders: 'pragma: no-cache\n' }
 win.webContents.loadURL('https://github.com', options)
@@ -1068,7 +1068,7 @@ Initiates a download of the resource at `url` without navigating. The
 
 Returns `string` - The URL of the current web page.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ width: 800, height: 600 })
 win.loadURL('https://github.com').then(() => {
@@ -1517,7 +1517,7 @@ 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
+```js
 const win = new BrowserWindow()
 win.webContents.on('found-in-page', (event, result) => {
   if (result.finalUpdate) win.webContents.stopFindInPage('clearSelection')
@@ -1637,7 +1637,7 @@ The `landscape` will be ignored if `@page` CSS at-rule is used in the web page.
 
 An example of `webContents.printToPDF`:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const fs = require('node:fs')
 const path = require('node:path')
@@ -1669,7 +1669,7 @@ See [Page.printToPdf](https://chromedevtools.github.io/devtools-protocol/tot/Pag
 Adds the specified path to DevTools workspace. Must be used after DevTools
 creation:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 win.webContents.on('devtools-opened', () => {
@@ -1992,7 +1992,7 @@ the cursor when dragging.
 
 Returns `Promise<void>` - resolves if the page is saved.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 

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

@@ -8,7 +8,7 @@ The `webFrameMain` module can be used to lookup frames across existing
 [`WebContents`](web-contents.md) instances. Navigation events are the common
 use case.
 
-```javascript
+```js
 const { BrowserWindow, webFrameMain } = require('electron')
 
 const win = new BrowserWindow({ width: 800, height: 1500 })
@@ -29,7 +29,7 @@ win.webContents.on(
 You can also access frames of existing pages by using the `mainFrame` property
 of [`WebContents`](web-contents.md).
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 async function main () {

+ 4 - 4
docs/api/web-frame.md

@@ -10,7 +10,7 @@ certain properties and methods (e.g. `webFrame.firstChild`).
 
 An example of zooming current page to 200%.
 
-```javascript
+```js
 const { webFrame } = require('electron')
 
 webFrame.setZoomFactor(2)
@@ -96,7 +96,7 @@ with an array of misspelt words when complete.
 
 An example of using [node-spellchecker][spellchecker] as provider:
 
-```javascript @ts-expect-error=[2,6]
+```js @ts-expect-error=[2,6]
 const { webFrame } = require('electron')
 const spellChecker = require('spellchecker')
 webFrame.setSpellCheckProvider('en-US', {
@@ -205,14 +205,14 @@ Returns `Object`:
 Returns an object describing usage information of Blink's internal memory
 caches.
 
-```javascript
+```js
 const { webFrame } = require('electron')
 console.log(webFrame.getResourceUsage())
 ```
 
 This will generate:
 
-```javascript
+```js
 {
   images: {
     count: 22,

+ 1 - 1
docs/api/web-request.md

@@ -23,7 +23,7 @@ called with a `response` object when `listener` has done its work.
 
 An example of adding `User-Agent` header for requests:
 
-```javascript
+```js
 const { session } = require('electron')
 
 // Modify the user agent for all requests to the following urls.

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

@@ -255,7 +255,7 @@ The `webview` tag has the following methods:
 
 **Example**
 
-```javascript @ts-expect-error=[3]
+```js @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('dom-ready', () => {
   webview.openDevTools()
@@ -802,7 +802,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 @ts-expect-error=[3]
+```js @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('console-message', (e) => {
   console.log('Guest page logged a message:', e.message)
@@ -823,7 +823,7 @@ Returns:
 Fired when a result is available for
 [`webview.findInPage`](#webviewfindinpagetext-options) request.
 
-```javascript @ts-expect-error=[3,6]
+```js @ts-expect-error=[3,6]
 const webview = document.querySelector('webview')
 webview.addEventListener('found-in-page', (e) => {
   webview.stopFindInPage('keepSelection')
@@ -948,7 +948,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 @ts-expect-error=[3]
+```js @ts-expect-error=[3]
 const webview = document.querySelector('webview')
 webview.addEventListener('close', () => {
   webview.src = 'about:blank'
@@ -968,7 +968,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 @ts-expect-error=[4,7]
+```js @ts-expect-error=[4,7]
 // In embedder page.
 const webview = document.querySelector('webview')
 webview.addEventListener('ipc-message', (event) => {
@@ -978,7 +978,7 @@ webview.addEventListener('ipc-message', (event) => {
 webview.send('ping')
 ```
 
-```javascript
+```js
 // In guest page.
 const { ipcRenderer } = require('electron')
 ipcRenderer.on('ping', () => {

+ 2 - 2
docs/api/window-open.md

@@ -80,7 +80,7 @@ window will not close when the opener window closes. The default value is `false
 
 ### Native `Window` example
 
-```javascript
+```js
 // main.js
 const mainWindow = new BrowserWindow()
 
@@ -104,7 +104,7 @@ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
 })
 ```
 
-```javascript
+```js
 // renderer process (mainWindow)
 const childWindow = window.open('', 'modal')
 childWindow.document.write('<h1>Hello</h1>')

+ 4 - 4
docs/breaking-changes.md

@@ -534,7 +534,7 @@ The `new-window` event of `<webview>` has been removed. There is no direct repla
 webview.addEventListener('new-window', (event) => {})
 ```
 
-```javascript fiddle='docs/fiddles/ipc/webview-new-window'
+```js
 // Replace with
 
 // main.js
@@ -1199,7 +1199,7 @@ module](https://medium.com/@nornagon/electrons-remote-module-considered-harmful-
 
 The APIs are now synchronous and the optional callback is no longer needed.
 
-```javascript
+```js
 // Deprecated
 protocol.unregisterProtocol(scheme, () => { /* ... */ })
 // Replace with
@@ -1228,7 +1228,7 @@ protocol.unregisterProtocol(scheme)
 
 The APIs are now synchronous and the optional callback is no longer needed.
 
-```javascript
+```js
 // Deprecated
 protocol.registerFileProtocol(scheme, handler, () => { /* ... */ })
 // Replace with
@@ -1243,7 +1243,7 @@ until navigation happens.
 This API is deprecated and users should use `protocol.isProtocolRegistered`
 and `protocol.isProtocolIntercepted` instead.
 
-```javascript
+```js
 // Deprecated
 protocol.isProtocolHandled(scheme).then(() => { /* ... */ })
 // Replace with

+ 1 - 1
docs/development/creating-api.md

@@ -164,7 +164,7 @@ An example of the contents of this file can be found [here](https://github.com/e
 
 Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so:
 
-```typescript title='lib/browser/api/module-list.ts' @ts-nocheck
+```ts title='lib/browser/api/module-list.ts' @ts-nocheck
 export const browserModuleList: ElectronInternal.ModuleEntry[] = [
   { name: 'apiName', loader: () => require('./api-name') },
 ];

+ 4 - 4
docs/faq.md

@@ -65,7 +65,7 @@ If you encounter this problem, the following articles may prove helpful:
 If you want a quick fix, you can make the variables global by changing your
 code from this:
 
-```javascript
+```js
 const { app, Tray } = require('electron')
 app.whenReady().then(() => {
   const tray = new Tray('/path/to/icon.png')
@@ -75,7 +75,7 @@ app.whenReady().then(() => {
 
 to this:
 
-```javascript
+```js
 const { app, Tray } = require('electron')
 let tray = null
 app.whenReady().then(() => {
@@ -92,7 +92,7 @@ for some libraries since they want to insert the symbols with the same names.
 
 To solve this, you can turn off node integration in Electron:
 
-```javascript
+```js
 // In the main process.
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({
@@ -141,7 +141,7 @@ Sub-pixel anti-aliasing needs a non-transparent background of the layer containi
 
 To achieve this goal, set the background in the constructor for [BrowserWindow][browser-window]:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({
   backgroundColor: '#fff'

+ 1 - 1
docs/tutorial/application-debugging.md

@@ -12,7 +12,7 @@ including instances of `BrowserWindow`, `BrowserView`, and `WebView`. You
 can open them programmatically by calling the `openDevTools()` API on the
 `webContents` of the instance:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow()

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

@@ -41,27 +41,27 @@ $ asar list /path/to/example.asar
 
 Read a file in the ASAR archive:
 
-```javascript
+```js
 const fs = require('node:fs')
 fs.readFileSync('/path/to/example.asar/file.txt')
 ```
 
 List all files under the root of the archive:
 
-```javascript
+```js
 const fs = require('node:fs')
 fs.readdirSync('/path/to/example.asar')
 ```
 
 Use a module from the archive:
 
-```javascript @ts-nocheck
+```js @ts-nocheck
 require('./path/to/example.asar/dir/module.js')
 ```
 
 You can also display a web page in an ASAR archive with `BrowserWindow`:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 
@@ -90,7 +90,7 @@ For some cases like verifying the ASAR archive's checksum, we need to read the
 content of an ASAR archive as a file. For this purpose you can use the built-in
 `original-fs` module which provides original `fs` APIs without `asar` support:
 
-```javascript
+```js
 const originalFs = require('original-fs')
 originalFs.readFileSync('/path/to/example.asar')
 ```
@@ -98,7 +98,7 @@ originalFs.readFileSync('/path/to/example.asar')
 You can also set `process.noAsar` to `true` to disable the support for `asar` in
 the `fs` module:
 
-```javascript
+```js
 const fs = require('node:fs')
 process.noAsar = true
 fs.readFileSync('/path/to/example.asar')

+ 9 - 9
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' @ts-nocheck
+```js 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' @ts-nocheck
+```js title='renderer.js' @ts-nocheck
 // use the exposed API in the renderer
 window.myAPI.doAThing()
 ```
@@ -34,7 +34,7 @@ window.myAPI.doAThing()
 
 There is a dedicated module in Electron to help you do this in a painless way. The [`contextBridge`](../api/context-bridge.md) module can be used to **safely** expose APIs from your preload script's isolated context to the context the website is running in. The API will also be accessible from the website on `window.myAPI` just like it was before.
 
-```javascript title='preload.js'
+```js title='preload.js'
 // preload with contextIsolation enabled
 const { contextBridge } = require('electron')
 
@@ -43,7 +43,7 @@ contextBridge.exposeInMainWorld('myAPI', {
 })
 ```
 
-```javascript title='renderer.js' @ts-nocheck
+```js title='renderer.js' @ts-nocheck
 // use the exposed API in the renderer
 window.myAPI.doAThing()
 ```
@@ -54,7 +54,7 @@ Please read the `contextBridge` documentation linked above to fully understand i
 
 Just enabling `contextIsolation` and using `contextBridge` does not automatically mean that everything you do is safe. For instance, this code is **unsafe**.
 
-```javascript title='preload.js'
+```js title='preload.js'
 // ❌ Bad code
 contextBridge.exposeInMainWorld('myAPI', {
   send: ipcRenderer.send
@@ -63,7 +63,7 @@ contextBridge.exposeInMainWorld('myAPI', {
 
 It directly exposes a powerful API without any kind of argument filtering. This would allow any website to send arbitrary IPC messages, which you do not want to be possible. The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.
 
-```javascript title='preload.js'
+```js title='preload.js'
 // ✅ Good code
 contextBridge.exposeInMainWorld('myAPI', {
   loadPreferences: () => ipcRenderer.invoke('load-prefs')
@@ -76,7 +76,7 @@ If you're building your Electron app with TypeScript, you'll want to add types t
 
 For example, given this `preload.ts` script:
 
-```typescript title='preload.ts'
+```ts title='preload.ts'
 contextBridge.exposeInMainWorld('electronAPI', {
   loadPreferences: () => ipcRenderer.invoke('load-prefs')
 })
@@ -84,7 +84,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
 
 You can create a `renderer.d.ts` declaration file and globally augment the `Window` interface:
 
-```typescript title='renderer.d.ts' @ts-noisolate
+```ts title='renderer.d.ts' @ts-noisolate
 export interface IElectronAPI {
   loadPreferences: () => Promise<void>,
 }
@@ -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'
+```ts title='renderer.ts'
 window.electronAPI.loadPreferences()
 ```
 

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

@@ -50,7 +50,7 @@ of this theming, due to the use of the macOS 10.14 SDK.
 This example demonstrates an Electron application that derives its theme colors from the
 `nativeTheme`. Additionally, it provides theme toggle and reset controls using IPC channels.
 
-```javascript fiddle='docs/fiddles/features/dark-mode'
+```fiddle docs/fiddles/features/dark-mode
 
 ```
 

+ 5 - 5
docs/tutorial/devices.md

@@ -26,7 +26,7 @@ This example demonstrates an Electron application that automatically selects
 the first available bluetooth device when the `Test Bluetooth` button is
 clicked.
 
-```javascript fiddle='docs/fiddles/features/web-bluetooth'
+```fiddle docs/fiddles/features/web-bluetooth
 
 ```
 
@@ -61,7 +61,7 @@ By default Electron employs the same [blocklist](https://github.com/WICG/webhid/
 used by Chromium.  If you wish to override this behavior, you can do so by
 setting the `disable-hid-blocklist` flag:
 
-```javascript
+```js
 app.commandLine.appendSwitch('disable-hid-blocklist')
 ```
 
@@ -72,7 +72,7 @@ HID devices through [`ses.setDevicePermissionHandler(handler)`](../api/session.m
 and through [`select-hid-device` event on the Session](../api/session.md#event-select-hid-device)
 when the `Test WebHID` button is clicked.
 
-```javascript fiddle='docs/fiddles/features/web-hid'
+```fiddle docs/fiddles/features/web-hid
 
 ```
 
@@ -112,7 +112,7 @@ as well as demonstrating selecting the first available Arduino Uno serial device
 [`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
 when the `Test Web Serial` button is clicked.
 
-```javascript fiddle='docs/fiddles/features/web-serial'
+```fiddle docs/fiddles/features/web-serial
 
 ```
 
@@ -152,6 +152,6 @@ USB devices (if they are attached) through [`ses.setDevicePermissionHandler(hand
 and through [`select-usb-device` event on the Session](../api/session.md#event-select-usb-device)
 when the `Test WebUSB` button is clicked.
 
-```javascript fiddle='docs/fiddles/features/web-usb'
+```fiddle docs/fiddles/features/web-usb
 
 ```

+ 1 - 1
docs/tutorial/devtools-extension.md

@@ -33,7 +33,7 @@ Using the [React Developer Tools][react-devtools] as an example:
 1. Pass the location of the extension to the [`ses.loadExtension`][load-extension]
    API. For React Developer Tools `v4.9.0`, it looks something like:
 
-   ```javascript
+   ```js
    const { app, session } = require('electron')
    const path = require('node:path')
    const os = require('node:os')

+ 0 - 9
docs/tutorial/examples.md

@@ -16,16 +16,7 @@ Once Fiddle is installed, you can press on the "Open in Fiddle" button that you
 will find below code samples like the following one:
 
 ```fiddle docs/fiddles/quick-start
-window.addEventListener('DOMContentLoaded', () => {
-  const replaceText = (selector, text) => {
-    const element = document.getElementById(selector)
-    if (element) element.innerText = text
-  }
 
-  for (const type of ['chrome', 'node', 'electron']) {
-    replaceText(`${type}-version`, process.versions[type])
-  }
-})
 ```
 
 If there is still something that you do not know how to do, please take a look at the [API][app]

+ 1 - 1
docs/tutorial/in-app-purchases.md

@@ -34,7 +34,7 @@ To test In-App Purchase in development with Electron you'll have to change the `
 
 Here is an example that shows how to use In-App Purchases in Electron. You'll have to replace the product ids by the identifiers of the products created with iTunes Connect (the identifier of `com.example.app.product1` is `product1`). Note that you have to listen to the `transactions-updated` event as soon as possible in your app.
 
-```javascript
+```js
 // Main process
 const { inAppPurchase } = require('electron')
 const PRODUCT_IDS = ['id1', 'id2']

+ 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 @ts-nocheck
+```js @ts-nocheck
 url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME
 ```
 

+ 18 - 18
docs/tutorial/ipc.md

@@ -50,7 +50,7 @@ sections.
 
 In the main process, set an IPC listener on the `set-title` channel with the `ipcMain.on` API:
 
-```javascript {6-10,22} title='main.js (Main Process)'
+```js {6-10,22} title='main.js (Main Process)'
 const { app, BrowserWindow, ipcMain } = require('electron')
 const path = require('node:path')
 
@@ -96,7 +96,7 @@ you need to choose which APIs to expose from your preload script using the `cont
 In your preload script, add the following code, which will expose a global `window.electronAPI`
 variable to your renderer process.
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInMainWorld('electronAPI', {
@@ -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)' @ts-expect-error=[4,5]
+```js title='renderer.js (Renderer Process)' @ts-expect-error=[4,5]
 const setButton = document.getElementById('btn')
 const titleInput = document.getElementById('title')
 setButton.addEventListener('click', () => {
@@ -181,7 +181,7 @@ provided to the renderer process. Please refer to
 [#24427](https://github.com/electron/electron/issues/24427) for details.
 :::
 
-```javascript {6-13,25} title='main.js (Main Process)'
+```js {6-13,25} title='main.js (Main Process)'
 const { app, BrowserWindow, dialog, ipcMain } = require('electron')
 const path = require('node:path')
 
@@ -225,7 +225,7 @@ In the preload script, we expose a one-line `openFile` function that calls and r
 `ipcRenderer.invoke('dialog:openFile')`. We'll be using this API in the next step to call the
 native dialog from our renderer's user interface.
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInMainWorld('electronAPI', {
@@ -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)' @ts-expect-error=[5]
+```js title='renderer.js (Renderer Process)' @ts-expect-error=[5]
 const btn = document.getElementById('btn')
 const filePathElement = document.getElementById('filePath')
 
@@ -299,7 +299,7 @@ The `ipcRenderer.send` API that we used for single-way communication can also be
 perform two-way communication. This was the recommended way for asynchronous two-way communication
 via IPC prior to Electron 7.
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 // You can also put expose this code to the renderer
 // process with the `contextBridge` API
 const { ipcRenderer } = require('electron')
@@ -310,7 +310,7 @@ ipcRenderer.on('asynchronous-reply', (_event, arg) => {
 ipcRenderer.send('asynchronous-message', 'ping')
 ```
 
-```javascript title='main.js (Main Process)'
+```js title='main.js (Main Process)'
 ipcMain.on('asynchronous-message', (event, arg) => {
   console.log(arg) // prints "ping" in the Node console
   // works like `send`, but returning a message back
@@ -332,7 +332,7 @@ channels, you would need to add additional app code to track each call and respo
 The `ipcRenderer.sendSync` API sends a message to the main process and waits _synchronously_ for a
 response.
 
-```javascript title='main.js (Main Process)'
+```js title='main.js (Main Process)'
 const { ipcMain } = require('electron')
 ipcMain.on('synchronous-message', (event, arg) => {
   console.log(arg) // prints "ping" in the Node console
@@ -340,7 +340,7 @@ ipcMain.on('synchronous-message', (event, arg) => {
 })
 ```
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 // You can also put expose this code to the renderer
 // process with the `contextBridge` API
 const { ipcRenderer } = require('electron')
@@ -376,7 +376,7 @@ For this demo, we'll need to first build a custom menu in the main process using
 module that uses the `webContents.send` API to send an IPC message from the main process to the
 target renderer.
 
-```javascript {11-26} title='main.js (Main Process)'
+```js {11-26} title='main.js (Main Process)'
 const { app, BrowserWindow, Menu, ipcMain } = require('electron')
 const path = require('node:path')
 
@@ -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 @ts-type={mainWindow:Electron.BrowserWindow}
+```js @ts-type={mainWindow:Electron.BrowserWindow}
 click: () => mainWindow.webContents.send('update-counter', -1)
 ```
 
@@ -425,7 +425,7 @@ Make sure you're loading the `index.html` and `preload.js` entry points for the
 Like in the previous renderer-to-main example, we use the `contextBridge` and `ipcRenderer`
 modules in the preload script to expose IPC functionality to the renderer process:
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInMainWorld('electronAPI', {
@@ -447,7 +447,7 @@ Use a custom handler that invoke the `callback` only with the desired arguments.
 In the case of this minimal example, you can call `ipcRenderer.on` directly in the preload script
 rather than exposing it over the context bridge.
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 const { ipcRenderer } = require('electron')
 
 window.addEventListener('DOMContentLoaded', () => {
@@ -488,7 +488,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)' @ts-window-type={electronAPI:{onUpdateCounter:(callback:(value:number)=>void)=>void}}
+```js title='renderer.js (Renderer Process)' @ts-window-type={electronAPI:{onUpdateCounter:(callback:(value:number)=>void)=>void}}
 const counter = document.getElementById('counter')
 
 window.electronAPI.onUpdateCounter((value) => {
@@ -511,7 +511,7 @@ We can demonstrate this with slight modifications to the code from the previous
 renderer process, expose another API to send a reply back to the main process through the
 `counter-value` channel.
 
-```javascript title='preload.js (Preload Script)'
+```js title='preload.js (Preload Script)'
 const { contextBridge, ipcRenderer } = require('electron')
 
 contextBridge.exposeInMainWorld('electronAPI', {
@@ -520,7 +520,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
 })
 ```
 
-```javascript title='renderer.js (Renderer Process)' @ts-window-type={electronAPI:{onUpdateCounter:(callback:(value:number)=>void)=>void,counterValue:(value:number)=>void}}
+```js title='renderer.js (Renderer Process)' @ts-window-type={electronAPI:{onUpdateCounter:(callback:(value:number)=>void)=>void,counterValue:(value:number)=>void}}
 const counter = document.getElementById('counter')
 
 window.electronAPI.onUpdateCounter((value) => {
@@ -533,7 +533,7 @@ window.electronAPI.onUpdateCounter((value) => {
 
 In the main process, listen for `counter-value` events and handle them appropriately.
 
-```javascript title='main.js (Main Process)'
+```js title='main.js (Main Process)'
 // ...
 ipcMain.on('counter-value', (_event, value) => {
   console.log(value) // will print value to Node console

+ 54 - 12
docs/tutorial/keyboard-shortcuts.md

@@ -14,11 +14,19 @@ To configure a local keyboard shortcut, you need to specify an [`accelerator`][]
 property when creating a [MenuItem][] within the [Menu][] module.
 
 Starting with a working application from the
-[Quick Start Guide](quick-start.md), update the `main.js` file with the
-following lines:
+[Quick Start Guide](quick-start.md), update the `main.js` to be:
+
+```fiddle docs/fiddles/features/keyboard-shortcuts/local
+const { app, BrowserWindow, Menu, MenuItem } = require('electron/main')
 
-```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/local'
-const { Menu, MenuItem } = require('electron')
+function createWindow () {
+  const win = new BrowserWindow({
+    width: 800,
+    height: 600
+  })
+
+  win.loadFile('index.html')
+}
 
 const menu = new Menu()
 menu.append(new MenuItem({
@@ -31,6 +39,20 @@ menu.append(new MenuItem({
 }))
 
 Menu.setApplicationMenu(menu)
+
+app.whenReady().then(createWindow)
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
+})
+
+app.on('activate', () => {
+  if (BrowserWindow.getAllWindows().length === 0) {
+    createWindow()
+  }
+})
 ```
 
 > NOTE: In the code above, you can see that the accelerator differs based on the
@@ -53,17 +75,37 @@ module to detect keyboard events even when the application does not have
 keyboard focus.
 
 Starting with a working application from the
-[Quick Start Guide](quick-start.md), update the `main.js` file with the
-following lines:
+[Quick Start Guide](quick-start.md), update the `main.js` to be:
+
+```fiddle docs/fiddles/features/keyboard-shortcuts/global
+const { app, BrowserWindow, globalShortcut } = require('electron/main')
 
-```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/global' @ts-type={createWindow:()=>void}
-const { app, globalShortcut } = require('electron')
+function createWindow () {
+  const win = new BrowserWindow({
+    width: 800,
+    height: 600
+  })
+
+  win.loadFile('index.html')
+}
 
 app.whenReady().then(() => {
   globalShortcut.register('Alt+CommandOrControl+I', () => {
     console.log('Electron loves global shortcuts!')
   })
 }).then(createWindow)
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
+})
+
+app.on('activate', () => {
+  if (BrowserWindow.getAllWindows().length === 0) {
+    createWindow()
+  }
+})
 ```
 
 > NOTE: In the code above, the `CommandOrControl` combination uses `Command`
@@ -81,8 +123,8 @@ If you want to handle keyboard shortcuts within a [BrowserWindow][], you can
 listen for the `keyup` and `keydown` [DOM events][dom-events] inside the
 renderer process using the [addEventListener() API][addEventListener-api].
 
-```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/web-apis|focus=renderer.js'
-const handleKeyPress = (event) => {
+```fiddle docs/fiddles/features/keyboard-shortcuts/web-apis|focus=renderer.js
+function handleKeyPress (event) {
   // You can put code here to handle the keypress.
   document.getElementById('last-keypress').innerText = event.key
   console.log(`You pressed ${event.key}`)
@@ -105,8 +147,8 @@ 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/interception-from-main'
-const { app, BrowserWindow } = require('electron')
+```fiddle docs/fiddles/features/keyboard-shortcuts/interception-from-main
+const { app, BrowserWindow } = require('electron/main')
 
 app.whenReady().then(() => {
   const win = new BrowserWindow({ width: 800, height: 600 })

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

@@ -25,14 +25,14 @@ we will use will be "`electron-fiddle://`".
 First, we will import the required modules from `electron`. These modules help
 control our application lifecycle and create a native browser window.
 
-```javascript
+```js
 const { app, BrowserWindow, shell } = require('electron')
 const path = require('node:path')
 ```
 
 Next, we will proceed to register our application to handle all "`electron-fiddle://`" protocols.
 
-```javascript
+```js
 if (process.defaultApp) {
   if (process.argv.length >= 2) {
     app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
@@ -44,7 +44,7 @@ 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
+```js
 let mainWindow
 
 const createWindow = () => {
@@ -67,7 +67,7 @@ This code will be different in Windows and Linux compared to MacOS. This is due
 
 #### Windows and Linux code:
 
-```javascript @ts-type={mainWindow:Electron.BrowserWindow} @ts-type={createWindow:()=>void}
+```js @ts-type={mainWindow:Electron.BrowserWindow} @ts-type={createWindow:()=>void}
 const gotTheLock = app.requestSingleInstanceLock()
 
 if (!gotTheLock) {
@@ -92,7 +92,7 @@ if (!gotTheLock) {
 
 #### MacOS code:
 
-```javascript @ts-type={createWindow:()=>void}
+```js @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.
@@ -108,7 +108,7 @@ app.on('open-url', (event, url) => {
 
 Finally, we will add some additional code to handle when someone closes our application.
 
-```javascript
+```js
 // Quit when all windows are closed, except on macOS. There, it's common
 // for applications and their menu bar to stay active until the user quits
 // explicitly with Cmd + Q.
@@ -167,7 +167,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 @ts-nocheck
+```js @ts-nocheck
 const packager = require('@electron/packager')
 
 packager({

+ 3 - 3
docs/tutorial/macos-dock.md

@@ -23,10 +23,10 @@ To set your custom dock menu, you need to use the
 [`app.dock.setMenu`](../api/dock.md#docksetmenumenu-macos) API,
 which is only available on macOS.
 
-```javascript fiddle='docs/fiddles/features/macos-dock-menu'
-const { app, BrowserWindow, Menu } = require('electron')
+```fiddle docs/fiddles/features/macos-dock-menu
+const { app, BrowserWindow, Menu } = require('electron/main')
 
-const createWindow = () => {
+function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600

+ 2 - 2
docs/tutorial/multithreading.md

@@ -9,7 +9,7 @@ It is possible to use Node.js features in Electron's Web Workers, to do
 so the `nodeIntegrationInWorker` option should be set to `true` in
 `webPreferences`.
 
-```javascript
+```js
 const win = new BrowserWindow({
   webPreferences: {
     nodeIntegrationInWorker: true
@@ -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 @ts-expect-error=[1]
+```js @ts-expect-error=[1]
 process.dlopen = () => {
   throw new Error('Load native module is not safe')
 }

+ 45 - 5
docs/tutorial/native-file-drag-drop.md

@@ -44,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 @ts-expect-error=[3]
+```js @ts-expect-error=[3]
 document.getElementById('drag').ondragstart = (event) => {
   event.preventDefault()
   window.electron.startDrag('drag-and-drop.md')
@@ -56,15 +56,55 @@ document.getElementById('drag').ondragstart = (event) => {
 In the Main process (`main.js` file), expand the received event with a path to the file that is
 being dragged and an icon:
 
-```javascript fiddle='docs/fiddles/features/drag-and-drop'
-const { ipcMain } = require('electron')
+```fiddle docs/fiddles/features/drag-and-drop
+const { app, BrowserWindow, ipcMain } = require('electron/main')
+const path = require('node:path')
+const fs = require('node:fs')
+const https = require('node:https')
+
+function createWindow () {
+  const win = new BrowserWindow({
+    width: 800,
+    height: 600,
+    webPreferences: {
+      preload: path.join(__dirname, 'preload.js')
+    }
+  })
+
+  win.loadFile('index.html')
+}
+
+const iconName = path.join(__dirname, 'iconForDragAndDrop.png')
+const icon = fs.createWriteStream(iconName)
+
+// Create a new file to copy - you can also copy existing files.
+fs.writeFileSync(path.join(__dirname, 'drag-and-drop-1.md'), '# First file to test drag and drop')
+fs.writeFileSync(path.join(__dirname, 'drag-and-drop-2.md'), '# Second file to test drag and drop')
+
+https.get('https://img.icons8.com/ios/452/drag-and-drop.png', (response) => {
+  response.pipe(icon)
+})
+
+app.whenReady().then(createWindow)
 
 ipcMain.on('ondragstart', (event, filePath) => {
   event.sender.startDrag({
-    file: filePath,
-    icon: '/path/to/icon.png'
+    file: path.join(__dirname, filePath),
+    icon: iconName
   })
 })
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
+})
+
+app.on('activate', () => {
+  if (BrowserWindow.getAllWindows().length === 0) {
+    createWindow()
+  }
+})
 ```
 
 After launching the Electron application, try dragging and dropping

+ 33 - 12
docs/tutorial/notifications.md

@@ -30,16 +30,38 @@ new Notification({
 
 Here's a full example that you can open with Electron Fiddle:
 
-```javascript fiddle='docs/fiddles/features/notifications/main'
-const { Notification } = require('electron')
+```fiddle docs/fiddles/features/notifications/main
+const { app, BrowserWindow, Notification } = require('electron/main')
+
+function createWindow () {
+  const win = new BrowserWindow({
+    width: 800,
+    height: 600
+  })
+
+  win.loadFile('index.html')
+}
 
 const NOTIFICATION_TITLE = 'Basic Notification'
 const NOTIFICATION_BODY = 'Notification from the Main process'
 
-new Notification({
-  title: NOTIFICATION_TITLE,
-  body: NOTIFICATION_BODY
-}).show()
+function showNotification () {
+  new Notification({ title: NOTIFICATION_TITLE, body: NOTIFICATION_BODY }).show()
+}
+
+app.whenReady().then(createWindow).then(showNotification)
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
+})
+
+app.on('activate', () => {
+  if (BrowserWindow.getAllWindows().length === 0) {
+    createWindow()
+  }
+})
 ```
 
 ### Show notifications in the renderer process
@@ -59,14 +81,13 @@ new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY }).onclick =
 
 Here's a full example that you can open with Electron Fiddle:
 
-```javascript fiddle='docs/fiddles/features/notifications/renderer'
+```fiddle docs/fiddles/features/notifications/renderer|focus=renderer.js
 const NOTIFICATION_TITLE = 'Title'
-const NOTIFICATION_BODY =
-  'Notification from the Renderer process. Click to log to console.'
-const CLICK_MESSAGE = 'Notification clicked'
+const NOTIFICATION_BODY = 'Notification from the Renderer process. Click to log to console.'
+const CLICK_MESSAGE = 'Notification clicked!'
 
-new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY }).onclick =
-  () => console.log(CLICK_MESSAGE)
+new window.Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY })
+  .onclick = () => { document.getElementById('output').innerText = CLICK_MESSAGE }
 ```
 
 ## Platform considerations

+ 28 - 6
docs/tutorial/offscreen-rendering.md

@@ -39,22 +39,44 @@ To enable this mode, GPU acceleration has to be disabled by calling the
 
 ## Example
 
-```javascript fiddle='docs/fiddles/features/offscreen-rendering'
-const { app, BrowserWindow } = require('electron')
+```fiddle docs/fiddles/features/offscreen-rendering
+const { app, BrowserWindow } = require('electron/main')
 const fs = require('node:fs')
+const path = require('node:path')
 
 app.disableHardwareAcceleration()
 
-let win
-
-app.whenReady().then(() => {
-  win = new BrowserWindow({ webPreferences: { offscreen: true } })
+function createWindow () {
+  const win = new BrowserWindow({
+    width: 800,
+    height: 600,
+    webPreferences: {
+      offscreen: true
+    }
+  })
 
   win.loadURL('https://github.com')
   win.webContents.on('paint', (event, dirty, image) => {
     fs.writeFileSync('ex.png', image.toPNG())
   })
   win.webContents.setFrameRate(60)
+  console.log(`The screenshot has been successfully saved to ${path.join(process.cwd(), 'ex.png')}`)
+}
+
+app.whenReady().then(() => {
+  createWindow()
+
+  app.on('activate', () => {
+    if (BrowserWindow.getAllWindows().length === 0) {
+      createWindow()
+    }
+  })
+})
+
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
 })
 ```
 

+ 8 - 5
docs/tutorial/progress-bar.md

@@ -50,12 +50,12 @@ See the [API documentation for more options and modes][setprogressbar].
 In this example, we add a progress bar to the main window that increments over time
 using Node.js timers.
 
-```javascript fiddle='docs/fiddles/features/progress-bar'
-const { app, BrowserWindow } = require('electron')
+```fiddle docs/fiddles/features/progress-bar
+const { app, BrowserWindow } = require('electron/main')
 
 let progressInterval
 
-const createWindow = () => {
+function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600
@@ -73,8 +73,11 @@ const createWindow = () => {
     win.setProgressBar(c)
 
     // increment or reset progress bar
-    if (c < 2) c += INCREMENT
-    else c = 0
+    if (c < 2) {
+      c += INCREMENT
+    } else {
+      c = (-INCREMENT * 5) // reset to a bit less than 0 to show reset state
+    }
   }, INTERVAL_DELAY)
 }
 

+ 4 - 4
docs/tutorial/recent-documents.md

@@ -24,12 +24,12 @@ the application via JumpList or dock menu, respectively.
 
 ### Managing recent documents
 
-```javascript fiddle='docs/fiddles/features/recent-documents'
-const { app, BrowserWindow } = require('electron')
+```fiddle docs/fiddles/features/recent-documents
+const { app, BrowserWindow } = require('electron/main')
 const fs = require('node:fs')
 const path = require('node:path')
 
-const createWindow = () => {
+function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600
@@ -116,7 +116,7 @@ following code snippet to your menu template:
 Make sure the application menu is added after the [`'ready'`](../api/app.md#event-ready)
 event and not before, or the menu item will be disabled:
 
-```javascript
+```js
 const { app, Menu } = require('electron')
 
 const template = [

+ 14 - 12
docs/tutorial/represented-file.md

@@ -27,22 +27,30 @@ To set the represented file of window, you can use the
 
 ## Example
 
-```javascript fiddle='docs/fiddles/features/represented-file'
-const { app, BrowserWindow } = require('electron')
+```fiddle docs/fiddles/features/represented-file
+const { app, BrowserWindow } = require('electron/main')
 const os = require('node:os')
 
-const createWindow = () => {
+function createWindow () {
   const win = new BrowserWindow({
     width: 800,
     height: 600
   })
+
+  win.setRepresentedFilename(os.homedir())
+  win.setDocumentEdited(true)
+
+  win.loadFile('index.html')
 }
 
 app.whenReady().then(() => {
-  const win = new BrowserWindow()
+  createWindow()
 
-  win.setRepresentedFilename(os.homedir())
-  win.setDocumentEdited(true)
+  app.on('activate', () => {
+    if (BrowserWindow.getAllWindows().length === 0) {
+      createWindow()
+    }
+  })
 })
 
 app.on('window-all-closed', () => {
@@ -50,12 +58,6 @@ app.on('window-all-closed', () => {
     app.quit()
   }
 })
-
-app.on('activate', () => {
-  if (BrowserWindow.getAllWindows().length === 0) {
-    createWindow()
-  }
-})
 ```
 
 After launching the Electron application, click on the title with `Command` or

+ 1 - 1
docs/tutorial/security.md

@@ -375,7 +375,7 @@ which can be set using Electron's
 [`webRequest.onHeadersReceived`](../api/web-request.md#webrequestonheadersreceivedfilter-listener)
 handler:
 
-```javascript title='main.js (Main Process)'
+```js title='main.js (Main Process)'
 const { session } = require('electron')
 
 session.defaultSession.webRequest.onHeadersReceived((details, callback) => {

+ 5 - 5
docs/tutorial/updates.md

@@ -81,14 +81,14 @@ You can use the [app.isPackaged](../api/app.md#appispackaged-readonly) API to ch
 
 :::
 
-```javascript title='main.js'
+```js title='main.js'
 const { app, autoUpdater, dialog } = require('electron')
 ```
 
 Next, construct the URL of the update server feed and tell
 [autoUpdater](../api/auto-updater.md) about it:
 
-```javascript title='main.js'
+```js title='main.js'
 const server = 'https://your-deployment-url.com'
 const url = `${server}/update/${process.platform}/${app.getVersion()}`
 
@@ -97,7 +97,7 @@ autoUpdater.setFeedURL({ url })
 
 As the final step, check for updates. The example below will check every minute:
 
-```javascript title='main.js'
+```js title='main.js'
 setInterval(() => {
   autoUpdater.checkForUpdates()
 }, 60000)
@@ -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" @ts-expect-error=[11]
+```js title="main.js" @ts-expect-error=[11]
 autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
   const dialogOpts = {
     type: 'info',
@@ -134,7 +134,7 @@ Also make sure that errors are
 [being handled](../api/auto-updater.md#event-error). Here's an example
 for logging them to `stderr`:
 
-```javascript title="main.js"
+```js title="main.js"
 autoUpdater.on('error', (message) => {
   console.error('There was a problem updating the application')
   console.error(message)

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

@@ -14,7 +14,7 @@ that are not a part of the web page.
 To create a frameless window, you need to set `frame` to `false` in the `BrowserWindow`
 constructor.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ frame: false })
 ```
@@ -28,7 +28,7 @@ option in the `BrowserWindow` constructor.
 Applying the `hidden` title bar style results in a hidden title bar and a full-size
 content window.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ titleBarStyle: 'hidden' })
 ```
@@ -44,7 +44,7 @@ The `customButtonsOnHover` title bar style will hide the traffic lights until yo
 over them. This is useful if you want to create custom traffic lights in your HTML but still
 use the native UI to control the window.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ titleBarStyle: 'customButtonsOnHover' })
 ```
@@ -57,7 +57,7 @@ options available.
 Applying `hiddenInset` title bar style will shift the vertical inset of the traffic lights
 by a fixed amount.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
 ```
@@ -66,7 +66,7 @@ If you need more granular control over the positioning of the traffic lights, yo
 a set of coordinates to the `trafficLightPosition` option in the `BrowserWindow`
 constructor.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({
   titleBarStyle: 'hidden',
@@ -80,7 +80,7 @@ You can also show and hide the traffic lights programmatically from the main pro
 The `win.setWindowButtonVisibility` forces traffic lights to be show or hidden depending
 on the value of its boolean parameter.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 // hides the traffic lights
@@ -106,7 +106,7 @@ The `titleBarOverlay` option accepts two different value formats.
 Specifying `true` on either platform will result in an overlay region with default
 system colors:
 
-```javascript title='main.js'
+```js title='main.js'
 // on macOS or Windows
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({
@@ -119,7 +119,7 @@ On either platform `titleBarOverlay` can also be an object. On both macOS and Wi
 
 If a color option is not specified, the color will default to its system color for the window control buttons. Similarly, if the height option is not specified it will default to the default height:
 
-```javascript title='main.js'
+```js title='main.js'
 // on Windows
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({
@@ -140,7 +140,7 @@ const win = new BrowserWindow({
 
 By setting the `transparent` option to `true`, you can make a fully transparent window.
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow({ transparent: true })
 ```
@@ -169,7 +169,7 @@ To create a click-through window, i.e. making the window ignore all mouse
 events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events]
 API:
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow } = require('electron')
 const win = new BrowserWindow()
 win.setIgnoreMouseEvents(true)
@@ -182,7 +182,7 @@ meaning that mouse movement events will not be emitted. On Windows and macOS, an
 optional parameter can be used to forward mouse move messages to the web page,
 allowing events such as `mouseleave` to be emitted:
 
-```javascript title='main.js'
+```js title='main.js'
 const { BrowserWindow, ipcMain } = require('electron')
 const path = require('node:path')
 
@@ -198,7 +198,7 @@ ipcMain.on('set-ignore-mouse-events', (event, ignore, options) => {
 })
 ```
 
-```javascript title='preload.js'
+```js title='preload.js'
 window.addEventListener('DOMContentLoaded', () => {
   const el = document.getElementById('clickThroughElement')
   el.addEventListener('mouseenter', () => {

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

@@ -60,7 +60,7 @@ Starting with a working application from the
 [Quick Start Guide](quick-start.md), update the `main.js` file with the
 following lines:
 
-```javascript
+```js
 const { app } = require('electron')
 
 app.setUserTasks([
@@ -80,7 +80,7 @@ app.setUserTasks([
 To clear your tasks list, you need to call `app.setUserTasks` with an empty
 array in the `main.js` file.
 
-```javascript
+```js
 const { app } = require('electron')
 
 app.setUserTasks([])
@@ -124,7 +124,7 @@ Starting with a working application from the
 [Quick Start Guide](quick-start.md), update the `main.js` file with the
 following lines:
 
-```javascript
+```js
 const { BrowserWindow, nativeImage } = require('electron')
 const path = require('node:path')
 
@@ -149,7 +149,7 @@ win.setThumbarButtons([
 To clear thumbnail toolbar buttons, you need to call
 `BrowserWindow.setThumbarButtons` with an empty array in the `main.js` file.
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow()
@@ -188,7 +188,7 @@ Starting with a working application from the
 [Quick Start Guide](quick-start.md), update the `main.js` file with the
 following lines:
 
-```javascript
+```js
 const { BrowserWindow, nativeImage } = require('electron')
 
 const win = new BrowserWindow()
@@ -217,7 +217,7 @@ Starting with a working application from the
 [Quick Start Guide](quick-start.md), update the `main.js` file with the
 following lines:
 
-```javascript
+```js
 const { BrowserWindow } = require('electron')
 
 const win = new BrowserWindow()

+ 2 - 2
package.json

@@ -89,10 +89,10 @@
     "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:ts-check-js-in-markdown && 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:markdown",
     "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:markdown": "node ./script/lint.js --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",

+ 429 - 174
script/lint.js

@@ -7,6 +7,7 @@ const { ESLint } = require('eslint');
 const fs = require('node:fs');
 const minimist = require('minimist');
 const path = require('node:path');
+const { getCodeBlocks } = require('@electron/lint-roller/dist/lib/markdown');
 
 const { chunkFilenames, findMatchingFiles } = require('./lib/utils');
 
@@ -19,11 +20,13 @@ const DEPOT_TOOLS = path.resolve(SOURCE_ROOT, 'third_party', 'depot_tools');
 // DEPOT_TOOLS in their path already
 process.env.PATH = `${process.env.PATH}${path.delimiter}${DEPOT_TOOLS}`;
 
-const IGNORELIST = new Set([
-  ['shell', 'browser', 'resources', 'win', 'resource.h'],
-  ['shell', 'common', 'node_includes.h'],
-  ['spec', 'fixtures', 'pages', 'jquery-3.6.0.min.js']
-].map(tokens => path.join(ELECTRON_ROOT, ...tokens)));
+const IGNORELIST = new Set(
+  [
+    ['shell', 'browser', 'resources', 'win', 'resource.h'],
+    ['shell', 'common', 'node_includes.h'],
+    ['spec', 'fixtures', 'pages', 'jquery-3.6.0.min.js']
+  ].map((tokens) => path.join(ELECTRON_ROOT, ...tokens))
+);
 
 const IS_WINDOWS = process.platform === 'win32';
 
@@ -74,11 +77,19 @@ function spawnAndCheckExitCode (cmd, args, opts) {
 
 function cpplint (args) {
   args.unshift(`--root=${SOURCE_ROOT}`);
-  const result = childProcess.spawnSync(IS_WINDOWS ? 'cpplint.bat' : 'cpplint.py', args, { encoding: 'utf8', shell: true });
+  const result = childProcess.spawnSync(
+    IS_WINDOWS ? 'cpplint.bat' : 'cpplint.py',
+    args,
+    { encoding: 'utf8', shell: true }
+  );
   // cpplint.py writes EVERYTHING to stderr, including status messages
   if (result.stderr) {
     for (const line of result.stderr.split(/[\r\n]+/)) {
-      if (line.length && !line.startsWith('Done processing ') && line !== 'Total errors found: 0') {
+      if (
+        line.length &&
+        !line.startsWith('Done processing ') &&
+        line !== 'Total errors found: 0'
+      ) {
         console.warn(line);
       }
     }
@@ -93,196 +104,408 @@ function isObjCHeader (filename) {
   return /\/(mac|cocoa)\//.test(filename);
 }
 
-const LINTERS = [{
-  key: 'cpp',
-  roots: ['shell'],
-  test: filename => filename.endsWith('.cc') || (filename.endsWith('.h') && !isObjCHeader(filename)),
-  run: (opts, filenames) => {
-    const clangFormatFlags = opts.fix ? ['--fix'] : [];
-    for (const chunk of chunkFilenames(filenames)) {
-      spawnAndCheckExitCode('python3', ['script/run-clang-format.py', ...clangFormatFlags, ...chunk]);
-      cpplint([`--filter=${CPPLINT_FILTERS.join(',')}`, ...chunk]);
-    }
-  }
-}, {
-  key: 'objc',
-  roots: ['shell'],
-  test: filename => filename.endsWith('.mm') || (filename.endsWith('.h') && isObjCHeader(filename)),
-  run: (opts, filenames) => {
-    const clangFormatFlags = opts.fix ? ['--fix'] : [];
-    spawnAndCheckExitCode('python3', ['script/run-clang-format.py', '-r', ...clangFormatFlags, ...filenames]);
-    const filter = [...CPPLINT_FILTERS, '-readability/braces'];
-    cpplint(['--extensions=mm,h', `--filter=${filter.join(',')}`, ...filenames]);
-  }
-}, {
-  key: 'python',
-  roots: ['script'],
-  test: filename => filename.endsWith('.py'),
-  run: (opts, filenames) => {
-    const rcfile = path.join(DEPOT_TOOLS, 'pylintrc');
-    const args = ['--rcfile=' + rcfile, ...filenames];
-    const env = { PYTHONPATH: path.join(ELECTRON_ROOT, 'script'), ...process.env };
-    spawnAndCheckExitCode('pylint-2.7', args, { env });
-  }
-}, {
-  key: 'javascript',
-  roots: ['build', 'default_app', 'lib', 'npm', 'script', 'spec'],
-  ignoreRoots: ['spec/node_modules'],
-  test: filename => filename.endsWith('.js') || filename.endsWith('.ts'),
-  run: async (opts, filenames) => {
-    const eslint = new ESLint({
-      // Do not use the lint cache on CI builds
-      cache: !process.env.CI,
-      cacheLocation: `node_modules/.eslintcache.${crypto.createHash('md5').update(fs.readFileSync(__filename)).digest('hex')}`,
-      extensions: ['.js', '.ts'],
-      fix: opts.fix,
-      overrideConfigFile: path.join(ELECTRON_ROOT, '.eslintrc.json'),
-      resolvePluginsRelativeTo: ELECTRON_ROOT
-    });
-    const formatter = await eslint.loadFormatter();
-    let successCount = 0;
-    const results = await eslint.lintFiles(filenames);
-    for (const result of results) {
-      successCount += result.errorCount === 0 ? 1 : 0;
-      if (opts.verbose && result.errorCount === 0 && result.warningCount === 0) {
-        console.log(`${result.filePath}: no errors or warnings`);
+const LINTERS = [
+  {
+    key: 'cpp',
+    roots: ['shell'],
+    test: (filename) =>
+      filename.endsWith('.cc') ||
+      (filename.endsWith('.h') && !isObjCHeader(filename)),
+    run: (opts, filenames) => {
+      const clangFormatFlags = opts.fix ? ['--fix'] : [];
+      for (const chunk of chunkFilenames(filenames)) {
+        spawnAndCheckExitCode('python3', [
+          'script/run-clang-format.py',
+          ...clangFormatFlags,
+          ...chunk
+        ]);
+        cpplint([`--filter=${CPPLINT_FILTERS.join(',')}`, ...chunk]);
       }
     }
-    console.log(formatter.format(results));
-    if (opts.fix) {
-      await ESLint.outputFixes(results);
+  },
+  {
+    key: 'objc',
+    roots: ['shell'],
+    test: (filename) =>
+      filename.endsWith('.mm') ||
+      (filename.endsWith('.h') && isObjCHeader(filename)),
+    run: (opts, filenames) => {
+      const clangFormatFlags = opts.fix ? ['--fix'] : [];
+      spawnAndCheckExitCode('python3', [
+        'script/run-clang-format.py',
+        '-r',
+        ...clangFormatFlags,
+        ...filenames
+      ]);
+      const filter = [...CPPLINT_FILTERS, '-readability/braces'];
+      cpplint([
+        '--extensions=mm,h',
+        `--filter=${filter.join(',')}`,
+        ...filenames
+      ]);
     }
-    if (successCount !== filenames.length) {
-      console.error('Linting had errors');
-      process.exit(1);
-    }
-  }
-}, {
-  key: 'gn',
-  roots: ['.'],
-  test: filename => filename.endsWith('.gn') || filename.endsWith('.gni'),
-  run: (opts, filenames) => {
-    const allOk = filenames.map(filename => {
+  },
+  {
+    key: 'python',
+    roots: ['script'],
+    test: (filename) => filename.endsWith('.py'),
+    run: (opts, filenames) => {
+      const rcfile = path.join(DEPOT_TOOLS, 'pylintrc');
+      const args = ['--rcfile=' + rcfile, ...filenames];
       const env = {
-        CHROMIUM_BUILDTOOLS_PATH: path.resolve(ELECTRON_ROOT, '..', 'buildtools'),
-        DEPOT_TOOLS_WIN_TOOLCHAIN: '0',
+        PYTHONPATH: path.join(ELECTRON_ROOT, 'script'),
         ...process.env
       };
-      // Users may not have depot_tools in PATH.
-      env.PATH = `${env.PATH}${path.delimiter}${DEPOT_TOOLS}`;
-      const args = ['format', filename];
-      if (!opts.fix) args.push('--dry-run');
-      const result = childProcess.spawnSync('gn', args, { env, stdio: 'inherit', shell: true });
-      if (result.status === 0) {
-        return true;
-      } else if (result.status === 2) {
-        console.log(`GN format errors in "${filename}". Run 'gn format "${filename}"' or rerun with --fix to fix them.`);
-        return false;
-      } else {
-        console.log(`Error running 'gn format --dry-run "${filename}"': exit code ${result.status}`);
-        return false;
-      }
-    }).every(x => x);
-    if (!allOk) {
-      process.exit(1);
+      spawnAndCheckExitCode('pylint-2.7', args, { env });
     }
-  }
-}, {
-  key: 'patches',
-  roots: ['patches'],
-  test: filename => filename.endsWith('.patch'),
-  run: (opts, filenames) => {
-    const patchesDir = path.resolve(__dirname, '../patches');
-    const patchesConfig = path.resolve(patchesDir, 'config.json');
-    // If the config does not exist, that's a problem
-    if (!fs.existsSync(patchesConfig)) {
-      console.error(`Patches config file: "${patchesConfig}" does not exist`);
-      process.exit(1);
+  },
+  {
+    key: 'javascript',
+    roots: ['build', 'default_app', 'lib', 'npm', 'script', 'spec'],
+    ignoreRoots: ['spec/node_modules'],
+    test: (filename) => filename.endsWith('.js') || filename.endsWith('.ts'),
+    run: async (opts, filenames) => {
+      const eslint = new ESLint({
+        // Do not use the lint cache on CI builds
+        cache: !process.env.CI,
+        cacheLocation: `node_modules/.eslintcache.${crypto
+          .createHash('md5')
+          .update(fs.readFileSync(__filename))
+          .digest('hex')}`,
+        extensions: ['.js', '.ts'],
+        fix: opts.fix,
+        overrideConfigFile: path.join(ELECTRON_ROOT, '.eslintrc.json'),
+        resolvePluginsRelativeTo: ELECTRON_ROOT
+      });
+      const formatter = await eslint.loadFormatter();
+      let successCount = 0;
+      const results = await eslint.lintFiles(filenames);
+      for (const result of results) {
+        successCount += result.errorCount === 0 ? 1 : 0;
+        if (
+          opts.verbose &&
+          result.errorCount === 0 &&
+          result.warningCount === 0
+        ) {
+          console.log(`${result.filePath}: no errors or warnings`);
+        }
+      }
+      console.log(formatter.format(results));
+      if (opts.fix) {
+        await ESLint.outputFixes(results);
+      }
+      if (successCount !== filenames.length) {
+        console.error('Linting had errors');
+        process.exit(1);
+      }
     }
-
-    const config = JSON.parse(fs.readFileSync(patchesConfig, 'utf8'));
-    for (const key of Object.keys(config)) {
-      // The directory the config points to should exist
-      const targetPatchesDir = path.resolve(__dirname, '../../..', key);
-      if (!fs.existsSync(targetPatchesDir)) {
-        console.error(`target patch directory: "${targetPatchesDir}" does not exist`);
+  },
+  {
+    key: 'gn',
+    roots: ['.'],
+    test: (filename) => filename.endsWith('.gn') || filename.endsWith('.gni'),
+    run: (opts, filenames) => {
+      const allOk = filenames
+        .map((filename) => {
+          const env = {
+            CHROMIUM_BUILDTOOLS_PATH: path.resolve(
+              ELECTRON_ROOT,
+              '..',
+              'buildtools'
+            ),
+            DEPOT_TOOLS_WIN_TOOLCHAIN: '0',
+            ...process.env
+          };
+          // Users may not have depot_tools in PATH.
+          env.PATH = `${env.PATH}${path.delimiter}${DEPOT_TOOLS}`;
+          const args = ['format', filename];
+          if (!opts.fix) args.push('--dry-run');
+          const result = childProcess.spawnSync('gn', args, {
+            env,
+            stdio: 'inherit',
+            shell: true
+          });
+          if (result.status === 0) {
+            return true;
+          } else if (result.status === 2) {
+            console.log(
+              `GN format errors in "${filename}". Run 'gn format "${filename}"' or rerun with --fix to fix them.`
+            );
+            return false;
+          } else {
+            console.log(
+              `Error running 'gn format --dry-run "${filename}"': exit code ${result.status}`
+            );
+            return false;
+          }
+        })
+        .every((x) => x);
+      if (!allOk) {
         process.exit(1);
       }
-      // We need a .patches file
-      const dotPatchesPath = path.resolve(targetPatchesDir, '.patches');
-      if (!fs.existsSync(dotPatchesPath)) {
-        console.error(`.patches file: "${dotPatchesPath}" does not exist`);
+    }
+  },
+  {
+    key: 'patches',
+    roots: ['patches'],
+    test: (filename) => filename.endsWith('.patch'),
+    run: (opts, filenames) => {
+      const patchesDir = path.resolve(__dirname, '../patches');
+      const patchesConfig = path.resolve(patchesDir, 'config.json');
+      // If the config does not exist, that's a problem
+      if (!fs.existsSync(patchesConfig)) {
+        console.error(`Patches config file: "${patchesConfig}" does not exist`);
         process.exit(1);
       }
 
-      // Read the patch list
-      const patchFileList = fs.readFileSync(dotPatchesPath, 'utf8').trim().split('\n');
-      const patchFileSet = new Set(patchFileList);
-      patchFileList.reduce((seen, file) => {
-        if (seen.has(file)) {
-          console.error(`'${file}' is listed in ${dotPatchesPath} more than once`);
+      const config = JSON.parse(fs.readFileSync(patchesConfig, 'utf8'));
+      for (const key of Object.keys(config)) {
+        // The directory the config points to should exist
+        const targetPatchesDir = path.resolve(__dirname, '../../..', key);
+        if (!fs.existsSync(targetPatchesDir)) {
+          console.error(
+            `target patch directory: "${targetPatchesDir}" does not exist`
+          );
+          process.exit(1);
+        }
+        // We need a .patches file
+        const dotPatchesPath = path.resolve(targetPatchesDir, '.patches');
+        if (!fs.existsSync(dotPatchesPath)) {
+          console.error(`.patches file: "${dotPatchesPath}" does not exist`);
           process.exit(1);
         }
-        return seen.add(file);
-      }, new Set());
 
-      if (patchFileList.length !== patchFileSet.size) {
-        console.error('Each patch file should only be in the .patches file once');
-        process.exit(1);
-      }
+        // Read the patch list
+        const patchFileList = fs
+          .readFileSync(dotPatchesPath, 'utf8')
+          .trim()
+          .split('\n');
+        const patchFileSet = new Set(patchFileList);
+        patchFileList.reduce((seen, file) => {
+          if (seen.has(file)) {
+            console.error(
+              `'${file}' is listed in ${dotPatchesPath} more than once`
+            );
+            process.exit(1);
+          }
+          return seen.add(file);
+        }, new Set());
+
+        if (patchFileList.length !== patchFileSet.size) {
+          console.error(
+            'Each patch file should only be in the .patches file once'
+          );
+          process.exit(1);
+        }
 
-      for (const file of fs.readdirSync(targetPatchesDir)) {
-        // Ignore the .patches file and READMEs
-        if (file === '.patches' || file === 'README.md') continue;
+        for (const file of fs.readdirSync(targetPatchesDir)) {
+          // Ignore the .patches file and READMEs
+          if (file === '.patches' || file === 'README.md') continue;
+
+          if (!patchFileSet.has(file)) {
+            console.error(
+              `Expected the .patches file at "${dotPatchesPath}" to contain a patch file ("${file}") present in the directory but it did not`
+            );
+            process.exit(1);
+          }
+          patchFileSet.delete(file);
+        }
 
-        if (!patchFileSet.has(file)) {
-          console.error(`Expected the .patches file at "${dotPatchesPath}" to contain a patch file ("${file}") present in the directory but it did not`);
+        // If anything is left in this set, it means it did not exist on disk
+        if (patchFileSet.size > 0) {
+          console.error(
+            `Expected all the patch files listed in the .patches file at "${dotPatchesPath}" to exist but some did not:\n${JSON.stringify(
+              [...patchFileSet.values()],
+              null,
+              2
+            )}`
+          );
           process.exit(1);
         }
-        patchFileSet.delete(file);
       }
 
-      // If anything is left in this set, it means it did not exist on disk
-      if (patchFileSet.size > 0) {
-        console.error(`Expected all the patch files listed in the .patches file at "${dotPatchesPath}" to exist but some did not:\n${JSON.stringify([...patchFileSet.values()], null, 2)}`);
+      const allOk =
+        filenames.length > 0 &&
+        filenames
+          .map((f) => {
+            const patchText = fs.readFileSync(f, 'utf8');
+            const subjectAndDescription =
+              /Subject: (.*?)\n\n([\s\S]*?)\s*(?=diff)/ms.exec(patchText);
+            if (!subjectAndDescription[2]) {
+              console.warn(
+                `Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`
+              );
+              return false;
+            }
+            const trailingWhitespaceLines = patchText
+              .split(/\r?\n/)
+              .map((line, index) => [line, index])
+              .filter(([line]) => line.startsWith('+') && /\s+$/.test(line))
+              .map(([, lineNumber]) => lineNumber + 1);
+            if (trailingWhitespaceLines.length > 0) {
+              console.warn(
+                `Patch file '${f}' has trailing whitespace on some lines (${trailingWhitespaceLines.join(
+                  ','
+                )}).`
+              );
+              return false;
+            }
+            return true;
+          })
+          .every((x) => x);
+      if (!allOk) {
         process.exit(1);
       }
     }
+  },
+  {
+    key: 'md',
+    roots: ['.'],
+    ignoreRoots: ['node_modules', 'spec/node_modules'],
+    test: (filename) => filename.endsWith('.md'),
+    run: async (opts, filenames) => {
+      let errors = false;
+
+      // Run markdownlint on all Markdown files
+      for (const chunk of chunkFilenames(filenames)) {
+        spawnAndCheckExitCode('electron-markdownlint', chunk);
+      }
 
-    const allOk = filenames.length > 0 && filenames.map(f => {
-      const patchText = fs.readFileSync(f, 'utf8');
-      const subjectAndDescription = /Subject: (.*?)\n\n([\s\S]*?)\s*(?=diff)/ms.exec(patchText);
-      if (!subjectAndDescription[2]) {
-        console.warn(`Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`);
-        return false;
+      // Run the remaining checks only in docs
+      const docs = filenames.filter(
+        (filename) => path.dirname(filename).split(path.sep)[0] === 'docs'
+      );
+
+      for (const filename of docs) {
+        const contents = fs.readFileSync(filename, 'utf8');
+        const codeBlocks = await getCodeBlocks(contents);
+
+        for (const codeBlock of codeBlocks) {
+          const line = codeBlock.position.start.line;
+
+          if (codeBlock.lang) {
+            // Enforce all lowercase language identifiers
+            if (codeBlock.lang.toLowerCase() !== codeBlock.lang) {
+              console.log(
+                `${filename}:${line} Code block language identifiers should be all lowercase`
+              );
+              errors = true;
+            }
+
+            // Prefer js/ts to javascript/typescript as the language identifier
+            if (codeBlock.lang === 'javascript') {
+              console.log(
+                `${filename}:${line} Use 'js' as code block language identifier instead of 'javascript'`
+              );
+              errors = true;
+            }
+
+            if (codeBlock.lang === 'typescript') {
+              console.log(
+                `${filename}:${line} Use 'typescript' as code block language identifier instead of 'ts'`
+              );
+              errors = true;
+            }
+
+            // Enforce latest fiddle code block syntax
+            if (
+              codeBlock.lang === 'javascript' &&
+              codeBlock.meta &&
+              codeBlock.meta.includes('fiddle=')
+            ) {
+              console.log(
+                `${filename}:${line} Use 'fiddle' as code block language identifier instead of 'javascript fiddle='`
+              );
+              errors = true;
+            }
+
+            // Ensure non-empty content in fiddle code blocks matches the file content
+            if (codeBlock.lang === 'fiddle' && codeBlock.value.trim() !== '') {
+              // This is copied and adapted from the website repo:
+              // https://github.com/electron/website/blob/62a55ca0dd14f97339e1a361b5418d2f11c34a75/src/transformers/fiddle-embedder.ts#L89C6-L101
+              const parseFiddleEmbedOptions = (optStrings) => {
+                // If there are optional parameters, parse them out to pass to the getFiddleAST method.
+                return optStrings.reduce((opts, option) => {
+                  // Use indexOf to support bizarre combinations like `|key=Myvalue=2` (which will properly
+                  // parse to {'key': 'Myvalue=2'})
+                  const firstEqual = option.indexOf('=');
+                  const key = option.slice(0, firstEqual);
+                  const value = option.slice(firstEqual + 1);
+                  return { ...opts, [key]: value };
+                }, {});
+              };
+
+              const [dir, ...others] = codeBlock.meta.split('|');
+              const options = parseFiddleEmbedOptions(others);
+
+              const fiddleFilename = path.join(dir, options.focus || 'main.js');
+
+              try {
+                const fiddleContent = fs
+                  .readFileSync(fiddleFilename, 'utf8')
+                  .trim();
+
+                if (fiddleContent !== codeBlock.value.trim()) {
+                  console.log(
+                    `${filename}:${line} Content for fiddle code block differs from content in ${fiddleFilename}`
+                  );
+                  errors = true;
+                }
+              } catch (err) {
+                console.error(
+                  `${filename}:${line} Error linting fiddle code block content`
+                );
+                if (err.stack) {
+                  console.error(err.stack);
+                }
+                errors = true;
+              }
+            }
+          }
+        }
       }
-      const trailingWhitespaceLines = patchText.split(/\r?\n/).map((line, index) => [line, index]).filter(([line]) => line.startsWith('+') && /\s+$/.test(line)).map(([, lineNumber]) => lineNumber + 1);
-      if (trailingWhitespaceLines.length > 0) {
-        console.warn(`Patch file '${f}' has trailing whitespace on some lines (${trailingWhitespaceLines.join(',')}).`);
-        return false;
+
+      if (errors) {
+        process.exit(1);
       }
-      return true;
-    }).every(x => x);
-    if (!allOk) {
-      process.exit(1);
     }
   }
-}];
+];
 
 function parseCommandLine () {
   let help;
-  const langs = ['cpp', 'objc', 'javascript', 'python', 'gn', 'patches'];
-  const langRoots = langs.map(lang => lang + '-roots');
-  const langIgnoreRoots = langs.map(lang => lang + '-ignore-roots');
+  const langs = [
+    'cpp',
+    'objc',
+    'javascript',
+    'python',
+    'gn',
+    'patches',
+    'markdown'
+  ];
+  const langRoots = langs.map((lang) => lang + '-roots');
+  const langIgnoreRoots = langs.map((lang) => lang + '-ignore-roots');
   const opts = minimist(process.argv.slice(2), {
     boolean: [...langs, 'help', 'changed', 'fix', 'verbose', 'only'],
-    alias: { cpp: ['c++', 'cc', 'cxx'], javascript: ['js', 'es'], python: 'py', changed: 'c', help: 'h', verbose: 'v' },
+    alias: {
+      cpp: ['c++', 'cc', 'cxx'],
+      javascript: ['js', 'es'],
+      python: 'py',
+      markdown: 'md',
+      changed: 'c',
+      help: 'h',
+      verbose: 'v'
+    },
     string: [...langRoots, ...langIgnoreRoots],
-    unknown: () => { help = true; }
+    unknown: () => {
+      help = true;
+    }
   });
   if (help || opts.help) {
-    const langFlags = langs.map(lang => `[--${lang}]`).join(' ');
-    console.log(`Usage: script/lint.js ${langFlags} [-c|--changed] [-h|--help] [-v|--verbose] [--fix] [--only -- file1 file2]`);
+    const langFlags = langs.map((lang) => `[--${lang}]`).join(' ');
+    console.log(
+      `Usage: script/lint.js ${langFlags} [-c|--changed] [-h|--help] [-v|--verbose] [--fix] [--only -- file1 file2]`
+    );
     process.exit(0);
   }
   return opts;
@@ -305,13 +528,19 @@ function populateLinterWithArgs (linter, opts) {
 }
 
 async function findChangedFiles (top) {
-  const result = await GitProcess.exec(['diff', '--name-only', '--cached'], top);
+  const result = await GitProcess.exec(
+    ['diff', '--name-only', '--cached'],
+    top
+  );
   if (result.exitCode !== 0) {
-    console.log('Failed to find changed files', GitProcess.parseError(result.stderr));
+    console.log(
+      'Failed to find changed files',
+      GitProcess.parseError(result.stderr)
+    );
     process.exit(1);
   }
   const relativePaths = result.stdout.split(/\r\n|\r|\n/g);
-  const absolutePaths = relativePaths.map(x => path.join(top, x));
+  const absolutePaths = relativePaths.map((x) => path.join(top, x));
   return new Set(absolutePaths);
 }
 
@@ -326,52 +555,78 @@ async function findFiles (args, linter) {
       return [];
     }
   } else if (args.only) {
-    includelist = new Set(args._.map(p => path.resolve(p)));
+    includelist = new Set(args._.map((p) => path.resolve(p)));
   }
 
   // accumulate the raw list of files
   for (const root of linter.roots) {
-    const files = await findMatchingFiles(path.join(ELECTRON_ROOT, root), linter.test);
+    const files = await findMatchingFiles(
+      path.join(ELECTRON_ROOT, root),
+      linter.test
+    );
     filenames.push(...files);
   }
 
-  for (const ignoreRoot of (linter.ignoreRoots) || []) {
+  for (const ignoreRoot of linter.ignoreRoots || []) {
     const ignorePath = path.join(ELECTRON_ROOT, ignoreRoot);
     if (!fs.existsSync(ignorePath)) continue;
 
-    const ignoreFiles = new Set(await findMatchingFiles(ignorePath, linter.test));
-    filenames = filenames.filter(fileName => !ignoreFiles.has(fileName));
+    const ignoreFiles = new Set(
+      await findMatchingFiles(ignorePath, linter.test)
+    );
+    filenames = filenames.filter((fileName) => !ignoreFiles.has(fileName));
   }
 
   // remove ignored files
-  filenames = filenames.filter(x => !IGNORELIST.has(x));
+  filenames = filenames.filter((x) => !IGNORELIST.has(x));
 
   // if a includelist exists, remove anything not in it
   if (includelist) {
-    filenames = filenames.filter(x => includelist.has(x));
+    filenames = filenames.filter((x) => includelist.has(x));
   }
 
   // it's important that filenames be relative otherwise clang-format will
   // produce patches with absolute paths in them, which `git apply` will refuse
   // to apply.
-  return filenames.map(x => path.relative(ELECTRON_ROOT, x));
+  return filenames.map((x) => path.relative(ELECTRON_ROOT, x));
 }
 
 async function main () {
   const opts = parseCommandLine();
 
   // no mode specified? run 'em all
-  if (!opts.cpp && !opts.javascript && !opts.objc && !opts.python && !opts.gn && !opts.patches) {
-    opts.cpp = opts.javascript = opts.objc = opts.python = opts.gn = opts.patches = true;
+  if (
+    !opts.cpp &&
+    !opts.javascript &&
+    !opts.objc &&
+    !opts.python &&
+    !opts.gn &&
+    !opts.patches &&
+    !opts.markdown
+  ) {
+    opts.cpp =
+      opts.javascript =
+      opts.objc =
+      opts.python =
+      opts.gn =
+      opts.patches =
+      opts.markdown =
+        true;
   }
 
-  const linters = LINTERS.filter(x => opts[x.key]);
+  const linters = LINTERS.filter((x) => opts[x.key]);
 
   for (const linter of linters) {
     populateLinterWithArgs(linter, opts);
     const filenames = await findFiles(opts, linter);
     if (filenames.length) {
-      if (opts.verbose) { console.log(`linting ${filenames.length} ${linter.key} ${filenames.length === 1 ? 'file' : 'files'}`); }
+      if (opts.verbose) {
+        console.log(
+          `linting ${filenames.length} ${linter.key} ${
+            filenames.length === 1 ? 'file' : 'files'
+          }`
+        );
+      }
       await linter.run(opts, filenames);
     }
   }

+ 3 - 3
yarn.lock

@@ -200,9 +200,9 @@
     "@octokit/rest" "^19.0.11"
 
 "@electron/lint-roller@^1.9.0":
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.9.0.tgz#784d2b9b7664dc7b696cc57cf6c9e181ea701e76"
-  integrity sha512-DgZJdzSAG19PotTPhWM4Xgt0RDv/nJS1JNKpJdvOUv2AsdU0rIq3kDAzm2AdzIW/AlrOnkpUA1UJNezdIAIA4g==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/@electron/lint-roller/-/lint-roller-1.10.0.tgz#278dfb285018869faabd5c3f616c64da7f2b26c1"
+  integrity sha512-dDJDy5MANWNIxv7OqQ0NwPzqwMmgTFYtjR/KKeEEdCQj1k21TT61+Zjdsm+IKbA2LSPujpujsyGKUQtb4oLLCA==
   dependencies:
     "@dsanders11/vscode-markdown-languageservice" "^0.3.0"
     balanced-match "^2.0.0"