From bad7e329df2c844270666f622a7f25122d33e8c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:43:05 -0800 Subject: [PATCH 1/2] Add @typescript-eslint/prefer-nullish-coalescing eslint rule --- addons/addon-image/src/ImageAddon.ts | 2 +- addons/addon-image/src/ImageRenderer.ts | 4 +- addons/addon-image/src/ImageStorage.ts | 10 ++-- addons/addon-ligatures/src/LigaturesAddon.ts | 2 +- addons/addon-ligatures/src/font.ts | 4 +- .../src/fontLigatures/processors/helper.ts | 20 +++---- .../fontLigatures/processors/substitution.ts | 5 +- addons/addon-search/src/SearchEngine.ts | 4 +- .../src/SerializeAddon.test.ts | 50 ++++++++--------- addons/addon-serialize/src/SerializeAddon.ts | 2 +- .../src/UnicodeGraphemesAddon.ts | 8 +-- addons/addon-web-links/src/WebLinksAddon.ts | 2 +- addons/addon-webgl/src/TextureAtlas.ts | 2 +- eslint.config.mjs | 8 +++ src/browser/CoreBrowserTerminal.ts | 8 +-- src/browser/RenderDebouncer.ts | 4 +- src/browser/TimeBasedDebouncer.ts | 4 +- src/browser/public/Terminal.ts | 10 +--- .../renderer/dom/DomRendererRowFactory.ts | 4 +- src/browser/services/RenderService.ts | 12 ++--- src/common/Event.ts | 53 ++++++++++--------- src/common/InputHandler.ts | 2 +- src/common/buffer/Buffer.ts | 12 ++--- src/common/buffer/BufferLine.ts | 2 +- src/common/input/Keyboard.test.ts | 2 +- src/common/input/KittyKeyboard.test.ts | 2 +- src/common/input/UnicodeV6.test.ts | 2 +- src/common/parser/ApcParser.ts | 4 +- src/common/parser/DcsParser.ts | 4 +- src/common/parser/EscapeSequenceParser.ts | 8 +-- src/common/parser/OscParser.ts | 4 +- src/headless/public/Terminal.ts | 8 +-- 32 files changed, 117 insertions(+), 151 deletions(-) diff --git a/addons/addon-image/src/ImageAddon.ts b/addons/addon-image/src/ImageAddon.ts index 167e93a7..6c7fe5ab 100644 --- a/addons/addon-image/src/ImageAddon.ts +++ b/addons/addon-image/src/ImageAddon.ts @@ -90,7 +90,7 @@ export class ImageAddon implements ITerminalAddon , IImageApi { // windowOptions.getCellSizePixels = true; // windowOptions.getWinSizeChars = true; // terminal.setOption('windowOptions', windowOptions); - const windowOps = terminal.options.windowOptions || {}; + const windowOps = terminal.options.windowOptions ?? {}; windowOps.getWinSizePixels = true; windowOps.getCellSizePixels = true; windowOps.getWinSizeChars = true; diff --git a/addons/addon-image/src/ImageRenderer.ts b/addons/addon-image/src/ImageRenderer.ts index e37169f3..1e87aa97 100644 --- a/addons/addon-image/src/ImageRenderer.ts +++ b/addons/addon-image/src/ImageRenderer.ts @@ -38,7 +38,7 @@ export class ImageRenderer extends Disposable implements IDisposable { * Only the DOM output canvas should be on the terminal's document, * which gets explicitly checked in `insertLayerToDom`. */ - const canvas = (localDocument || document).createElement('canvas'); + const canvas = (localDocument ?? document).createElement('canvas'); canvas.width = width | 0; canvas.height = height | 0; return canvas; @@ -242,7 +242,7 @@ export class ImageRenderer extends Disposable implements IDisposable { } if (!this._placeholder) return; this._ctx.drawImage( - this._placeholderBitmap || this._placeholder!, + this._placeholderBitmap ?? this._placeholder!, col * width, (row * height) % 2 ? 0 : 1, // needs %2 offset correction width * count, diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index f9b6eef6..059849ea 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -381,7 +381,7 @@ export class ImageStorage implements IDisposable { if (!line) return; for (let col = 0; col < cols; ++col) { if (line.getBg(col) & BgFlags.HAS_EXTENDED) { - let e: IExtendedAttrsImage = line._extendedAttrs[col] || EMPTY_ATTRS; + let e: IExtendedAttrsImage = line._extendedAttrs[col] ?? EMPTY_ATTRS; const imageId = e.imageId; if (imageId === undefined || imageId === -1) { continue; @@ -400,7 +400,7 @@ export class ImageStorage implements IDisposable { while ( ++col < cols && (line.getBg(col) & BgFlags.HAS_EXTENDED) - && (e = line._extendedAttrs[col] || EMPTY_ATTRS) + && (e = line._extendedAttrs[col] ?? EMPTY_ATTRS) && (e.imageId === imageId) && (e.tileId === startTile + count) ) { @@ -442,7 +442,7 @@ export class ImageStorage implements IDisposable { for (let row = 0; row < rows; ++row) { const line = buffer.lines.get(row) as IBufferLineExt; if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) { - const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] || EMPTY_ATTRS; + const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS; const imageId = e.imageId; if (imageId === undefined || imageId === -1) { continue; @@ -487,7 +487,7 @@ export class ImageStorage implements IDisposable { const buffer = this._terminal._core.buffer; const line = buffer.lines.get(y) as IBufferLineExt; if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) { - const e: IExtendedAttrsImage = line._extendedAttrs[x] || EMPTY_ATTRS; + const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS; if (e.imageId && e.imageId !== -1) { const orig = this._images.get(e.imageId)?.orig; if (window.ImageBitmap && orig instanceof ImageBitmap) { @@ -507,7 +507,7 @@ export class ImageStorage implements IDisposable { const buffer = this._terminal._core.buffer; const line = buffer.lines.get(y) as IBufferLineExt; if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) { - const e: IExtendedAttrsImage = line._extendedAttrs[x] || EMPTY_ATTRS; + const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS; if (e.imageId && e.imageId !== -1 && e.tileId !== -1) { const spec = this._images.get(e.imageId); if (spec) { diff --git a/addons/addon-ligatures/src/LigaturesAddon.ts b/addons/addon-ligatures/src/LigaturesAddon.ts index 2bde9cf6..463b0d26 100644 --- a/addons/addon-ligatures/src/LigaturesAddon.ts +++ b/addons/addon-ligatures/src/LigaturesAddon.ts @@ -22,7 +22,7 @@ export class LigaturesAddon implements ITerminalAddon , ILigaturesApi { constructor(options?: Partial) { // Source: calt set from https://github.com/be5invis/Iosevka?tab=readme-ov-file#ligations - this._fallbackLigatures = (options?.fallbackLigatures || [ + this._fallbackLigatures = (options?.fallbackLigatures ?? [ '<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->', '<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=', '<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '::', ':::', diff --git a/addons/addon-ligatures/src/font.ts b/addons/addon-ligatures/src/font.ts index 196651ac..1316932a 100644 --- a/addons/addon-ligatures/src/font.ts +++ b/addons/addon-ligatures/src/font.ts @@ -80,9 +80,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi console.error(err.name, err.message); } } - if (!fontsPromise) { - fontsPromise = Promise.resolve({}); - } + fontsPromise ??= Promise.resolve({}); } const fonts = await fontsPromise; diff --git a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts index 3f06d672..e6259b6d 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts @@ -52,12 +52,10 @@ export function processLookaheadPosition( } processedEntries.add(currentEntry.entry); - if (!currentEntry.entry.forward) { - currentEntry.entry.forward = { - individual: {}, - range: [] - }; - } + currentEntry.entry.forward ??= { + individual: {}, + range: [] + }; // All glyphs at this position share ONE entry - lookahead just needs to match, // all paths lead to the same result @@ -97,12 +95,10 @@ export function processBacktrackPosition( } processedEntries.add(currentEntry.entry); - if (!currentEntry.entry.reverse) { - currentEntry.entry.reverse = { - individual: {}, - range: [] - }; - } + currentEntry.entry.reverse ??= { + individual: {}, + range: [] + }; // All glyphs at this position share ONE entry - backtrack just needs to match, // all paths lead to the same result diff --git a/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts b/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts index 5eae2476..3a41e936 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts @@ -54,9 +54,6 @@ export function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId return (glyphId + table.deltaGlyphId) % (2 ** 16); // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2 case 2: - // eslint-disable-next-line eqeqeq - return table.substitute[coverageIndex] != null - ? table.substitute[coverageIndex] - : null; + return table.substitute[coverageIndex] ?? null; } } diff --git a/addons/addon-search/src/SearchEngine.ts b/addons/addon-search/src/SearchEngine.ts index a78be61e..b9991974 100644 --- a/addons/addon-search/src/SearchEngine.ts +++ b/addons/addon-search/src/SearchEngine.ts @@ -198,9 +198,7 @@ export class SearchEngine { } } - if (!result) { - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - } + result ??= this._findInLine(term, searchPosition, searchOptions, isReverseSearch); // Search from startRow - 1 to top if (!result) { diff --git a/addons/addon-serialize/src/SerializeAddon.test.ts b/addons/addon-serialize/src/SerializeAddon.test.ts index e5307e1a..6e8c2ad4 100644 --- a/addons/addon-serialize/src/SerializeAddon.test.ts +++ b/addons/addon-serialize/src/SerializeAddon.test.ts @@ -228,7 +228,7 @@ describe('SerializeAddon', () => { it('empty terminal with selection turned off', () => { const output = serializeAddon.serializeAsHTML(); assert.notEqual(output, ''); - assert.equal((output.match(/
{10}<\/span><\/div>/g) || []).length, 2); + assert.equal((output.match(/
{10}<\/span><\/div>/g) ?? []).length, 2); }); it('empty terminal with no selection', () => { @@ -245,7 +245,7 @@ describe('SerializeAddon', () => { const output = serializeAddon.serializeAsHTML({ onlySelection: true }); - assert.equal((output.match(/
terminal<\/span><\/div>/g) || []).length, 1, output); + assert.equal((output.match(/
terminal<\/span><\/div>/g) ?? []).length, 1, output); }); it('basic terminal with html unsafe chars', async () => { @@ -255,7 +255,7 @@ describe('SerializeAddon', () => { const output = serializeAddon.serializeAsHTML({ onlySelection: true }); - assert.equal((output.match(/
<a>&pi;<\/span><\/div>/g) || []).length, 1, output); + assert.equal((output.match(/
<a>&pi;<\/span><\/div>/g) ?? []).length, 1, output); }); it('serializes rows within a provided range', async () => { @@ -267,7 +267,7 @@ describe('SerializeAddon', () => { startCol: 4 } }); - const rowMatches = output.match(/
.*?<\/span><\/div>/g) || []; + const rowMatches = output.match(/
.*?<\/span><\/div>/g) ?? []; assert.equal(rowMatches.length, 1, output); assert.ok(rowMatches[0]?.includes('hello')); assert.ok(!output.includes('bye')); @@ -278,56 +278,56 @@ describe('SerializeAddon', () => { await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with italic styling', async () => { await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with inverse styling', async () => { await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with underline styling', async () => { await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with double underline styling', async () => { await writeP(terminal, ' ' + sgr('4:2') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with curly underline styling', async () => { await writeP(terminal, ' ' + sgr('4:3') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with dotted underline styling', async () => { await writeP(terminal, ' ' + sgr('4:4') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with dashed underline styling', async () => { await writeP(terminal, ' ' + sgr('4:5') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with underline color (palette)', async () => { @@ -350,49 +350,49 @@ describe('SerializeAddon', () => { await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with dim styling', async () => { await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with strikethrough styling', async () => { await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with combined styling', async () => { await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/ <\/span>/g) || []).length, 1, output); - assert.equal((output.match(/termi<\/span>/g) || []).length, 1, output); - assert.equal((output.match(/nal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/ <\/span>/g) ?? []).length, 1, output); + assert.equal((output.match(/termi<\/span>/g) ?? []).length, 1, output); + assert.equal((output.match(/nal<\/span>/g) ?? []).length, 1, output); }); it('cells with color styling', async () => { await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with background styling', async () => { await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('empty terminal with default options', async () => { const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: monospace; font-size: 15px;/g) || []).length, 1, output); + assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: monospace; font-size: 15px;/g) ?? []).length, 1, output); }); it('empty terminal with custom options', async () => { @@ -405,14 +405,14 @@ describe('SerializeAddon', () => { const output = serializeAddon.serializeAsHTML({ includeGlobalBackground: true }); - assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output); + assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) ?? []).length, 1, output); }); it('empty terminal with background included', async () => { const output = serializeAddon.serializeAsHTML({ includeGlobalBackground: true }); - assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: monospace; font-size: 15px;/g) || []).length, 1, output); + assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: monospace; font-size: 15px;/g) ?? []).length, 1, output); }); it('cells with custom color styling', async () => { @@ -422,7 +422,7 @@ describe('SerializeAddon', () => { await writeP(terminal, ' ' + sgr('38;5;0') + 'terminal' + sgr('39') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); it('cells with color styling - xterm headless', async () => { @@ -432,7 +432,7 @@ describe('SerializeAddon', () => { await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) ?? []).length, 1, output); }); }); }); diff --git a/addons/addon-serialize/src/SerializeAddon.ts b/addons/addon-serialize/src/SerializeAddon.ts index c9c66ce8..4b06ae70 100644 --- a/addons/addon-serialize/src/SerializeAddon.ts +++ b/addons/addon-serialize/src/SerializeAddon.ts @@ -601,7 +601,7 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi { throw new Error('Cannot use addon until it has been loaded'); } - return this._serializeBufferAsHTML(this._terminal, options || {}); + return this._serializeBufferAsHTML(this._terminal, options ?? {}); } public dispose(): void { } diff --git a/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts b/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts index 6e4d2266..e40f2076 100644 --- a/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts +++ b/addons/addon-unicode-graphemes/src/UnicodeGraphemesAddon.ts @@ -16,12 +16,8 @@ export class UnicodeGraphemesAddon implements ITerminalAddon , IUnicodeGraphemes private _oldVersion: string = ''; public activate(terminal: Terminal): void { - if (! this._provider15) { - this._provider15 = new UnicodeGraphemeProvider(false); - } - if (! this._provider15Graphemes) { - this._provider15Graphemes = new UnicodeGraphemeProvider(true); - } + this._provider15 ??= new UnicodeGraphemeProvider(false); + this._provider15Graphemes ??= new UnicodeGraphemeProvider(true); const unicode = terminal.unicode; this._unicode = unicode; unicode.register(this._provider15); diff --git a/addons/addon-web-links/src/WebLinksAddon.ts b/addons/addon-web-links/src/WebLinksAddon.ts index b3f0548c..56491e85 100644 --- a/addons/addon-web-links/src/WebLinksAddon.ts +++ b/addons/addon-web-links/src/WebLinksAddon.ts @@ -48,7 +48,7 @@ export class WebLinksAddon implements ITerminalAddon , IWebLinksApi { public activate(terminal: Terminal): void { this._terminal = terminal; const options = this._options as ILinkProviderOptions; - const regex = options.urlRegex || strictUrlRegex; + const regex = options.urlRegex ?? strictUrlRegex; this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); } diff --git a/addons/addon-webgl/src/TextureAtlas.ts b/addons/addon-webgl/src/TextureAtlas.ts index 3486b2e0..aa0e2653 100644 --- a/addons/addon-webgl/src/TextureAtlas.ts +++ b/addons/addon-webgl/src/TextureAtlas.ts @@ -410,7 +410,7 @@ export class TextureAtlas implements ITextureAtlas { const cache = this._getContrastCache(dim); const adjustedColor = cache.getColor(bg, fg); if (adjustedColor !== undefined) { - return adjustedColor || undefined; + return adjustedColor ?? undefined; } const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse); diff --git a/eslint.config.mjs b/eslint.config.mjs index 6cf78ecd..d630c9dc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -66,6 +66,7 @@ export default tseslint.config( ], '@typescript-eslint/no-confusing-void-expression': ['warn', { ignoreArrowShorthand: true }], '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }], '@typescript-eslint/prefer-namespace-keyword': 'warn', '@typescript-eslint/no-unused-vars': ['warn', { vars: 'all', args: 'none' }], '@typescript-eslint/no-require-imports': 'off', @@ -140,5 +141,12 @@ export default tseslint.config( '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-member-accessibility': 'off' } + }, + { + // Disable prefer-nullish-coalescing for files without strictNullChecks + files: ['demo/**/*.ts', '**/*.benchmark.ts'], + rules: { + '@typescript-eslint/prefer-nullish-coalescing': 'off' + } } ); diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 869a545a..bb45cb64 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -793,15 +793,15 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (!(events & CoreMouseEventType.UP)) { this._document!.removeEventListener('mouseup', requestedEvents.mouseup!); requestedEvents.mouseup = null; - } else if (!requestedEvents.mouseup) { - requestedEvents.mouseup = eventListeners.mouseup; + } else { + requestedEvents.mouseup ??= eventListeners.mouseup; } if (!(events & CoreMouseEventType.DRAG)) { this._document!.removeEventListener('mousemove', requestedEvents.mousedrag!); requestedEvents.mousedrag = null; - } else if (!requestedEvents.mousedrag) { - requestedEvents.mousedrag = eventListeners.mousedrag; + } else { + requestedEvents.mousedrag ??= eventListeners.mousedrag; } })); // force initial onProtocolChange so we dont miss early mouse requests diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index dd3b97a6..c7ddb6ce 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -40,8 +40,8 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback { public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void { this._rowCount = rowCount; // Get the min/max row start/end for the arg values - rowStart = rowStart !== undefined ? rowStart : 0; - rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1; + rowStart = rowStart ?? 0; + rowEnd = rowEnd ?? this._rowCount - 1; // Set the properties to the updated values this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; diff --git a/src/browser/TimeBasedDebouncer.ts b/src/browser/TimeBasedDebouncer.ts index 4d7a65a1..99731290 100644 --- a/src/browser/TimeBasedDebouncer.ts +++ b/src/browser/TimeBasedDebouncer.ts @@ -37,8 +37,8 @@ export class TimeBasedDebouncer implements IRenderDebouncer { public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void { this._rowCount = rowCount; // Get the min/max row start/end for the arg values - rowStart = rowStart !== undefined ? rowStart : 0; - rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1; + rowStart = rowStart ?? 0; + rowEnd = rowEnd ?? this._rowCount - 1; // Set the properties to the updated values this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 6b4cdee2..4a77fd4a 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -84,10 +84,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get element(): HTMLElement | undefined { return this._core.element; } public get parser(): IParser { - if (!this._parser) { - this._parser = new ParserApi(this._core); - } - return this._parser; + return this._parser ??= new ParserApi(this._core); } public get unicode(): IUnicodeHandling { this._checkProposedApi(); @@ -97,10 +94,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } public get buffer(): IBufferNamespaceApi { - if (!this._buffer) { - this._buffer = this._register(new BufferNamespaceApi(this._core)); - } - return this._buffer; + return this._buffer ??= this._register(new BufferNamespaceApi(this._core)); } public get markers(): ReadonlyArray { return this._core.markers; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 96d3f171..e95d221b 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -494,8 +494,8 @@ export class DomRendererRowFactory { // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from // non-dim cells const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1); - adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, ratio); - cache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null); + adjustedColor = color.ensureContrastRatio(bgOverride ?? bg, fgOverride ?? fg, ratio); + cache.setColor((bgOverride ?? bg).rgba, (fgOverride ?? fg).rgba, adjustedColor ?? null); } if (adjustedColor) { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 1cced406..848eebcc 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -347,13 +347,11 @@ class SynchronizedOutputHandler { this._end = Math.max(this._end, end); } - if (this._timeout === undefined) { - this._timeout = this._coreBrowserService.window.setTimeout(() => { - this._timeout = undefined; - this._coreService.decPrivateModes.synchronizedOutput = false; - this._onTimeout(); - }, Constants.SYNCHRONIZED_OUTPUT_TIMEOUT_MS); - } + this._timeout ??= this._coreBrowserService.window.setTimeout(() => { + this._timeout = undefined; + this._coreService.decPrivateModes.synchronizedOutput = false; + this._onTimeout(); + }, Constants.SYNCHRONIZED_OUTPUT_TIMEOUT_MS); } public flush(): { start: number, end: number } | undefined { diff --git a/src/common/Event.ts b/src/common/Event.ts index 9ea39b9c..f9fcb4e7 100644 --- a/src/common/Event.ts +++ b/src/common/Event.ts @@ -18,33 +18,34 @@ export class Emitter { private _event: IEvent | undefined; public get event(): IEvent { - if (!this._event) { - this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { - if (this._disposed) { - return toDisposable(() => {}); - } - - const entry = { fn: listener, thisArgs }; - this._listeners.push(entry); - - const result = toDisposable(() => { - const idx = this._listeners.indexOf(entry); - if (idx !== -1) { - this._listeners.splice(idx, 1); - } - }); - - if (disposables) { - if (Array.isArray(disposables)) { - disposables.push(result); - } else { - disposables.add(result); - } - } - - return result; - }; + if (this._event) { + return this._event; } + this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { + if (this._disposed) { + return toDisposable(() => {}); + } + + const entry = { fn: listener, thisArgs }; + this._listeners.push(entry); + + const result = toDisposable(() => { + const idx = this._listeners.indexOf(entry); + if (idx !== -1) { + this._listeners.splice(idx, 1); + } + }); + + if (disposables) { + if (Array.isArray(disposables)) { + disposables.push(result); + } else { + disposables.add(result); + } + } + + return result; + }; return this._event; } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index ed74520d..1bb923e6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -3343,7 +3343,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (collectAndFlag[0] === '/') { return true; // TODO: Is this supported? } - this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); + this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] ?? DEFAULT_CHARSET); return true; } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index ee0374f7..b738ffef 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -122,9 +122,7 @@ export class Buffer implements IBuffer { */ public fillViewportRows(fillAttr?: IAttributeData): void { if (this.lines.length === 0) { - if (fillAttr === undefined) { - fillAttr = DEFAULT_ATTR_DATA; - } + fillAttr ??= DEFAULT_ATTR_DATA; let i = this._rows; while (i--) { this.lines.push(this.getBlankLine(fillAttr)); @@ -589,9 +587,7 @@ export class Buffer implements IBuffer { * @param x The position to move the cursor to the previous tab stop. */ public prevStop(x?: number): number { - if (x === null || x === undefined) { - x = this.x; - } + x ??= this.x; while (!this.tabs[--x] && x > 0); return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; } @@ -601,9 +597,7 @@ export class Buffer implements IBuffer { * @param x The position to move the cursor one tab stop forward. */ public nextStop(x?: number): number { - if (x === null || x === undefined) { - x = this.x; - } + x ??= this.x; while (!this.tabs[++x] && x < this._cols); return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; } diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index ee3481a2..03177076 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -66,7 +66,7 @@ export class BufferLine implements IBufferLine { constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) { this._data = new Uint32Array(cols * CELL_SIZE); - const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); + const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { this.setCell(i, cell); } diff --git a/src/common/input/Keyboard.test.ts b/src/common/input/Keyboard.test.ts index 8c7f0dbc..47f516f0 100644 --- a/src/common/input/Keyboard.test.ts +++ b/src/common/input/Keyboard.test.ts @@ -26,7 +26,7 @@ function testEvaluateKeyboardEvent(partialEvent: { ctrlKey: partialEvent.ctrlKey || false, shiftKey: partialEvent.shiftKey || false, metaKey: partialEvent.metaKey || false, - keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : 0, + keyCode: partialEvent.keyCode ?? 0, code: partialEvent.code || '', key: partialEvent.key || '', type: partialEvent.type || '' diff --git a/src/common/input/KittyKeyboard.test.ts b/src/common/input/KittyKeyboard.test.ts index e346708f..706eec15 100644 --- a/src/common/input/KittyKeyboard.test.ts +++ b/src/common/input/KittyKeyboard.test.ts @@ -9,7 +9,7 @@ function createEvent(partialEvent: Partial = {}): IKeyboardEvent ctrlKey: partialEvent.ctrlKey || false, shiftKey: partialEvent.shiftKey || false, metaKey: partialEvent.metaKey || false, - keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : 0, + keyCode: partialEvent.keyCode ?? 0, code: partialEvent.code || '', key: partialEvent.key || '', type: partialEvent.type || 'keydown' diff --git a/src/common/input/UnicodeV6.test.ts b/src/common/input/UnicodeV6.test.ts index 9f89048c..98dc97dc 100644 --- a/src/common/input/UnicodeV6.test.ts +++ b/src/common/input/UnicodeV6.test.ts @@ -169,7 +169,7 @@ it('wcwidth should match all values from the old implementation', function(): vo if (num < 127) { return 1; } - const t = table || initTable(); + const t = table ?? initTable(); if (num < 65536) { return t[num >> 4] >> ((num & 15) << 1) & 3; } diff --git a/src/common/parser/ApcParser.ts b/src/common/parser/ApcParser.ts index 81f26860..e5a82ce3 100644 --- a/src/common/parser/ApcParser.ts +++ b/src/common/parser/ApcParser.ts @@ -36,9 +36,7 @@ export class ApcParser implements IApcParser { * @param handler The handler to register */ public registerHandler(ident: number, handler: IApcHandler): IDisposable { - if (this._handlers[ident] === undefined) { - this._handlers[ident] = []; - } + this._handlers[ident] ??= []; const handlerList = this._handlers[ident]; handlerList.push(handler); return { diff --git a/src/common/parser/DcsParser.ts b/src/common/parser/DcsParser.ts index b66524ba..182179b0 100644 --- a/src/common/parser/DcsParser.ts +++ b/src/common/parser/DcsParser.ts @@ -29,9 +29,7 @@ export class DcsParser implements IDcsParser { } public registerHandler(ident: number, handler: IDcsHandler): IDisposable { - if (this._handlers[ident] === undefined) { - this._handlers[ident] = []; - } + this._handlers[ident] ??= []; const handlerList = this._handlers[ident]; handlerList.push(handler); return { diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index c192be43..bb99efe1 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -362,9 +362,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable { const ident = this._identifier(id, [0x30, 0x7e]); - if (this._escHandlers[ident] === undefined) { - this._escHandlers[ident] = []; - } + this._escHandlers[ident] ??= []; const handlerList = this._escHandlers[ident]; handlerList.push(handler); return { @@ -395,9 +393,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable { const ident = this._identifier(id); - if (this._csiHandlers[ident] === undefined) { - this._csiHandlers[ident] = []; - } + this._csiHandlers[ident] ??= []; const handlerList = this._csiHandlers[ident]; handlerList.push(handler); return { diff --git a/src/common/parser/OscParser.ts b/src/common/parser/OscParser.ts index 32710aed..2a829a8e 100644 --- a/src/common/parser/OscParser.ts +++ b/src/common/parser/OscParser.ts @@ -23,9 +23,7 @@ export class OscParser implements IOscParser { }; public registerHandler(ident: number, handler: IOscHandler): IDisposable { - if (this._handlers[ident] === undefined) { - this._handlers[ident] = []; - } + this._handlers[ident] ??= []; const handlerList = this._handlers[ident]; handlerList.push(handler); return { diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index cdb94180..72614933 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -84,9 +84,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get onWriteParsed(): IEvent { return this._core.onWriteParsed; } public get parser(): IParser { - if (!this._parser) { - this._parser = new ParserApi(this._core); - } + this._parser ??= new ParserApi(this._core); return this._parser; } public get unicode(): IUnicodeHandling { @@ -96,9 +94,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } public get buffer(): IBufferNamespaceApi { - if (!this._buffer) { - this._buffer = this._register(new BufferNamespaceApi(this._core)); - } + this._buffer ??= this._register(new BufferNamespaceApi(this._core)); return this._buffer; } public get markers(): ReadonlyArray { From 0e679ea6aed689ef3daea9ea379e2977483ee2c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:56:53 -0800 Subject: [PATCH 2/2] Use nullish coalescing in KeyboardService --- src/browser/services/KeyboardService.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/browser/services/KeyboardService.ts b/src/browser/services/KeyboardService.ts index d9e6c1ee..a6150df3 100644 --- a/src/browser/services/KeyboardService.ts +++ b/src/browser/services/KeyboardService.ts @@ -24,16 +24,12 @@ export class KeyboardService implements IKeyboardService { } private _getWin32InputMode(): Win32InputMode { - if (!this._win32InputMode) { - this._win32InputMode = new Win32InputMode(); - } + this._win32InputMode ??= new Win32InputMode(); return this._win32InputMode; } private _getKittyKeyboard(): KittyKeyboard { - if (!this._kittyKeyboard) { - this._kittyKeyboard = new KittyKeyboard(); - } + this._kittyKeyboard ??= new KittyKeyboard(); return this._kittyKeyboard; }