From c5f9144c3476d5aaf91dbf67463cae5bd7173cda Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 13 Jan 2023 19:22:25 -0500 Subject: [PATCH] The War of The Types (#4490) --- docs/API.md | 3 +- docs/AddingACompiler.md | 2 +- lib/base-compiler.ts | 7 +- lib/common-utils.ts | 34 +++ static/.eslintrc.js | 2 + static/ansi-to-html.ts | 22 +- static/assert.ts | 11 + static/compiler-service.ts | 8 +- static/components.interfaces.ts | 9 +- static/components.ts | 12 +- static/event-map.ts | 2 +- static/formatter-registry.ts | 3 +- static/graph-layout-core.ts | 8 +- static/hub.ts | 6 +- static/modes/llvm-ir-mode.ts | 5 +- static/modes/mlir-mode.ts | 2 +- static/multifile-service.ts | 4 +- static/options.interfaces.ts | 3 + static/options.ts | 6 +- static/panes/ast-view.ts | 5 +- static/panes/cfg-view.ts | 6 +- static/panes/compiler.ts | 234 ++++++++++-------- static/panes/conformance-view.ts | 18 +- static/panes/device-view.ts | 6 +- static/panes/diff.ts | 2 +- static/panes/editor.ts | 47 ++-- static/panes/flags-view.ts | 2 +- static/panes/gccdump-view.ts | 6 +- static/panes/ir-view.ts | 5 +- static/panes/llvm-opt-pipeline.ts | 26 +- static/panes/output.ts | 28 +-- static/panes/pane.ts | 5 +- static/panes/pp-view.ts | 7 +- static/panes/tree.ts | 26 +- static/settings.ts | 25 +- static/widgets/compiler-picker.ts | 4 +- static/widgets/history-widget.ts | 26 +- static/widgets/libs-widget.ts | 3 +- static/widgets/load-save.ts | 18 +- static/widgets/site-templates-widget.ts | 11 +- static/widgets/timing-info-widget.ts | 10 +- types/compiler.interfaces.ts | 37 ++- .../assembly-documentation.interfaces.ts | 12 +- types/features/filters.interfaces.ts | 1 + 44 files changed, 416 insertions(+), 303 deletions(-) create mode 100644 lib/common-utils.ts diff --git a/docs/API.md b/docs/API.md index 84e2fdf8014..ca3c6ee8270 100644 --- a/docs/API.md +++ b/docs/API.md @@ -122,7 +122,8 @@ probably not useful for most REST users. To force a cache bypass, set `bypassCache` in the root of the request to `true`. -Filters include `binary`, `binaryObject`, `labels`, `intel`, `directives` and `demangle`, which correspond to the UI buttons on the HTML version. +Filters include `binary`, `binaryObject`, `labels`, `intel`, `directives` and `demangle`, which correspond to the UI +buttons on the HTML version. With the tools array you can ask CE to execute certain tools available for the current compiler, and also supply arguments for this tool. diff --git a/docs/AddingACompiler.md b/docs/AddingACompiler.md index 6ef100dc2c6..3704d472982 100644 --- a/docs/AddingACompiler.md +++ b/docs/AddingACompiler.md @@ -85,7 +85,7 @@ once the site runs on the Amazon environment, the `&clang` group **will not** ha ### Configuration keys | Key Name | Type | Description | -|----------------------|------------|------------------------------------------------------------------------------------------------------------------| +| -------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------- | | name | String | Human readable name of the compiler | | exe | Path | Path to the executable | | alias | Identifier | Another identifier for this compiler (mostly deprecated, used for backwards compatibility with very old CE URLs) | diff --git a/lib/base-compiler.ts b/lib/base-compiler.ts index 4ca5d9dd512..494f77f9150 100644 --- a/lib/base-compiler.ts +++ b/lib/base-compiler.ts @@ -137,8 +137,11 @@ export class BaseCompiler implements ICompiler { if (!this.compiler.supportsOptOutput) this.compiler.supportsOptOutput = false; if (!this.compiler.disabledFilters) this.compiler.disabledFilters = []; - else if (typeof this.compiler.disabledFilters === 'string') - this.compiler.disabledFilters = this.compiler.disabledFilters.split(','); + else if (typeof (this.compiler.disabledFilters as any) === 'string') { + // When first loaded from props it may be a string so we split it here + // I'd like a better way to do this that doesn't involve type hacks + this.compiler.disabledFilters = (this.compiler.disabledFilters as any).split(','); + } this.asm = new AsmParser(this.compilerProps); this.llvmIr = new LlvmIrParser(this.compilerProps); diff --git a/lib/common-utils.ts b/lib/common-utils.ts new file mode 100644 index 00000000000..76aa049cf5a --- /dev/null +++ b/lib/common-utils.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +export function isString(x: any): x is string { + return typeof x === 'string' || x instanceof String; +} + +// Object.keys is typed as returning :string[] for some reason +// This util is for cases where the key is a union of a few possible keys and we +// want the resulting array properly typed. +export function keys(o: Record): K[] { + return Object.keys(o) as K[]; +} diff --git a/static/.eslintrc.js b/static/.eslintrc.js index 8829583e78d..d23a261813d 100644 --- a/static/.eslintrc.js +++ b/static/.eslintrc.js @@ -64,6 +64,8 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', // Too much js code still exists '@typescript-eslint/ban-ts-comment': 'off', // We need some @ts-ignore at some points '@typescript-eslint/no-unnecessary-condition': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/prefer-includes': 'error', }, }, ], diff --git a/static/ansi-to-html.ts b/static/ansi-to-html.ts index 42e36b04b8e..bfe1818f12d 100644 --- a/static/ansi-to-html.ts +++ b/static/ansi-to-html.ts @@ -29,6 +29,8 @@ import _ from 'underscore'; import {AnsiToHtmlOptions, ColorCodes} from './ansi-to-html.interfaces'; +import {assert, unwrap} from './assert'; +import {isString} from '../lib/common-utils'; const defaults: AnsiToHtmlOptions = { fg: '#FFF', @@ -120,16 +122,16 @@ function toColorHexString(ref: number[]): string { function generateOutput(stack: string[], token: string, data: string | number, options: AnsiToHtmlOptions): string { if (token === 'text') { - // Note: Param 'data' must be a string at this point - return pushText(data as string, options); + assert(isString(data), "Param 'data' must be a string at this point"); + return pushText(data, options); } else if (token === 'display') { return handleDisplay(stack, data, options); } else if (token === 'xterm256') { - // Note: Param 'data' must be a string at this point - return handleXterm256(stack, data as string, options); + assert(isString(data), "Param 'data' must be a string at this point"); + return handleXterm256(stack, data, options); } else if (token === 'rgb') { - // Note: Param 'data' must be a string at this point - return handleRgb(stack, data as string, options); + assert(isString(data), "Param 'data' must be a string at this point"); + return handleRgb(stack, data, options); } return ''; } @@ -162,7 +164,7 @@ function handleXterm256(stack: string[], data: string, options: AnsiToHtmlOption } function handleDisplay(stack: string[], _code: string | number, options: AnsiToHtmlOptions): string { - const code: number = parseInt(_code as string, 10); + const code: number = isString(_code) ? parseInt(_code, 10) : _code; const codeMap: Record string> = { '-1': () => '
', // @ts-ignore @@ -176,8 +178,8 @@ function handleDisplay(stack: string[], _code: string | number, options: AnsiToH 22: () => closeTag(stack, 'b'), 23: () => closeTag(stack, 'i'), 24: () => closeTag(stack, 'u'), - 39: () => pushForegroundColor(stack, options.fg as string), - 49: () => pushBackgroundColor(stack, options.bg as string), + 39: () => pushForegroundColor(stack, unwrap(options.fg)), + 49: () => pushBackgroundColor(stack, unwrap(options.bg)), }; if (code in codeMap) { @@ -249,7 +251,7 @@ function notCategory(category: string): (e: StickyStackElement) => boolean { * @returns the ansi token type */ function categoryForCode(_code: string | number): string { - const code: number = parseInt(_code as string, 10); + const code: number = isString(_code) ? parseInt(_code, 10) : _code; if (code === 0) { return 'all'; diff --git a/static/assert.ts b/static/assert.ts index 4c704a8275a..3870dd36acb 100644 --- a/static/assert.ts +++ b/static/assert.ts @@ -22,6 +22,7 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. +import {isString} from '../lib/common-utils'; import stacktrace from '../lib/stacktrace'; // This file defines three assert utilities: @@ -78,3 +79,13 @@ export function unwrap(x: T | undefined | null, message?: string, ...extra_in } return x; } + +// This mainly a utility for JQuery.val(): string | number | string[] | undefined, in our code we typically want a +// single string. +// T is syntax sugar for unwrapping to a string union +export function unwrapString(x: any, message?: string, ...extra_info: any[]): T { + if (!isString(x)) { + fail('String unwrap failed', message, extra_info); + } + return x as T; +} diff --git a/static/compiler-service.ts b/static/compiler-service.ts index ed0be2482a9..1f6e0fd320a 100644 --- a/static/compiler-service.ts +++ b/static/compiler-service.ts @@ -79,7 +79,7 @@ export class CompilerService { const foundCompiler = this.findCompiler(langId, compilerId); if (!foundCompiler) { - const compilers = Object.values(this.getCompilersForLang(langId)); + const compilers = Object.values(this.getCompilersForLang(langId) ?? {}); if (compilers.length > 0) { return { compiler: compilers[0], @@ -144,8 +144,8 @@ export class CompilerService { .value(); } - getCompilersForLang(langId: string): Record { - return langId in this.compilersByLang ? this.compilersByLang[langId] : {}; + getCompilersForLang(langId: string): Record | undefined { + return langId in this.compilersByLang ? this.compilersByLang[langId] : undefined; } private findCompilerInList(compilers: Record, compilerId: string) { @@ -162,7 +162,7 @@ export class CompilerService { findCompiler(langId: string, compilerId: string): CompilerInfo | null { if (!compilerId) return null; - const compilers = this.getCompilersForLang(langId); + const compilers = this.getCompilersForLang(langId) ?? {}; return this.findCompilerInList(compilers, compilerId); } diff --git a/static/components.interfaces.ts b/static/components.interfaces.ts index 65ed06c11bf..0dce4e9ec3e 100644 --- a/static/components.interfaces.ts +++ b/static/components.interfaces.ts @@ -61,14 +61,15 @@ export interface ComponentConfig { } type StateWithLanguage = {lang: string}; -type StateWithEditor = {source: number}; +// TODO(#4490 The War of The Types) We should normalize state types +type StateWithEditor = {source: string | number}; type StateWithTree = {tree: number}; type StateWithId = {id: number}; type EmptyState = Record; export type EmptyCompilerState = StateWithLanguage & StateWithEditor; export type PopulatedCompilerState = StateWithEditor & { - filters: CompilerOutputOptions; + filters: CompilerOutputOptions | undefined; options: unknown; compiler: string; libs?: unknown; @@ -95,12 +96,12 @@ export type PopulatedEditorState = StateWithId & { export type EmptyTreeState = Partial; export type OutputState = StateWithTree & { - compiler: string; + compiler: number; // CompilerID editor: number; // EditorId }; export type ToolViewState = StateWithTree & { - compiler: string; + compiler: number; // CompilerId editor: number; // EditorId toolId: string; args: unknown; diff --git a/static/components.ts b/static/components.ts index 88015096c95..2497077cf55 100644 --- a/static/components.ts +++ b/static/components.ts @@ -127,7 +127,7 @@ export function getCompiler(editorId: number, lang: string): ComponentConfig { } /** Get an output component with the given configuration. */ -export function getOutput(compiler: string, editor: number, tree: number): ComponentConfig { +export function getOutput(compiler: number, editor: number, tree: number): ComponentConfig { return { type: 'component', componentName: OUTPUT_COMPONENT_NAME, @@ -269,7 +269,7 @@ export function getOutput(compiler: string, editor: number, tree: number): Compo /** Get a tool view component with the given configuration. */ export function getToolViewWith( - compiler: string, + compiler: number, editor: number, toolId: string, args: string, @@ -353,7 +353,7 @@ export function getOptView(): ComponentConfig { /** Get an opt view with the given configuration. */ export function getOptViewWith( id: number, - source: number, + source: string, optOutput: unknown, compilerName: string, editorid: number, @@ -411,7 +411,7 @@ export function getPpView(): ComponentConfig { /** Get a preprocessor view with the given configuration. */ export function getPpViewWith( id: number, - source: number, + source: string, ppOutput: unknown, compilerName: string, editorid: number, @@ -443,7 +443,7 @@ export function getAstView(): ComponentConfig { /** Get an ast view with the given configuration. */ export function getAstViewWith( id: number, - source: number, + source: string, astOutput: unknown, compilerName: string, editorid: number, diff --git a/static/event-map.ts b/static/event-map.ts index 687e25ae1ea..f98be0b8d4b 100644 --- a/static/event-map.ts +++ b/static/event-map.ts @@ -86,7 +86,7 @@ export type EventMap = { ) => void; executorClose: (executorId: number) => void; executorOpen: (executorId: number, editorId: boolean | number) => void; - filtersChange: (compilerId: number, filters: CompilerOutputOptions) => void; + filtersChange: (compilerId: number, filters: Partial) => void; findCompilers: () => void; findEditors: () => void; findExecutors: () => void; diff --git a/static/formatter-registry.ts b/static/formatter-registry.ts index 52b790f6566..4dfcc370192 100644 --- a/static/formatter-registry.ts +++ b/static/formatter-registry.ts @@ -28,6 +28,7 @@ import {Alert} from './widgets/alert'; import {Settings} from './settings'; import {FormattingRequest} from './api/formatting.interfaces'; import {getFormattedCode} from './api/api'; +import {unwrap} from './assert'; // Proxy function to emit the error to the alert system const onFormatError = (cause: string, source: string) => { @@ -44,7 +45,7 @@ const doFormatRequest = async (options: FormattingRequest) => { const body = await res.json(); if (res.status === 200 && body.exit === 0) { // API sent 200 and we have a valid response - return body.answer as string; + return unwrap(body.answer); } // We had an error (either HTTP request error, or API error) // Figure out which it is, show it to the user, and reject the promise diff --git a/static/graph-layout-core.ts b/static/graph-layout-core.ts index a9b44f83c64..4093d7ef429 100644 --- a/static/graph-layout-core.ts +++ b/static/graph-layout-core.ts @@ -655,12 +655,16 @@ export class GraphLayoutCore { // Throw everything away and do it all again, but smarter for (const edgeColumn of this.edgeColumns) { for (const intervalTree of edgeColumn.intervals) { - intervalTree.root = null as unknown as Node; + // Note: Incorrect types in the interval tree library + // After https://github.com/alexbol99/flatten-interval-tree/pull/42 this can be intervalTree.clear() + (intervalTree.root as Node | null) = null; } } for (const edgeRow of this.edgeRows) { for (const intervalTree of edgeRow.intervals) { - intervalTree.root = null as unknown as Node; + // Note: Incorrect types in the interval tree library + // After https://github.com/alexbol99/flatten-interval-tree/pull/42 this can be intervalTree.clear() + (intervalTree.root as Node | null) = null; } } // Edge kind is the primary heuristic for subrow/column assignment diff --git a/static/hub.ts b/static/hub.ts index d1802c6427e..ba56fc92468 100644 --- a/static/hub.ts +++ b/static/hub.ts @@ -91,7 +91,7 @@ export class Hub { public trees: Tree[] = []; public editors: any[] = []; // typeof Editor - public readonly compilerService: any; // typeof CompilerService + public readonly compilerService: CompilerService; public deferred = true; public deferredEmissions: unknown[][] = []; @@ -340,8 +340,8 @@ export class Hub { } public addAtRoot(elem: GoldenLayout.ContentItem): void { - const rootFirstItem = this.layout.root.contentItems[0] as GoldenLayout.ContentItem | undefined; - if (rootFirstItem) { + if (this.layout.root.contentItems.length > 0) { + const rootFirstItem = this.layout.root.contentItems[0]; if (rootFirstItem.isRow || rootFirstItem.isColumn) { rootFirstItem.addChild(elem); } else { diff --git a/static/modes/llvm-ir-mode.ts b/static/modes/llvm-ir-mode.ts index 087192822f1..4121b4cbb42 100644 --- a/static/modes/llvm-ir-mode.ts +++ b/static/modes/llvm-ir-mode.ts @@ -22,13 +22,12 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -'use strict'; -const monaco = require('monaco-editor'); +import * as monaco from 'monaco-editor'; // This definition is based on the official LLVM vim syntax: // http://llvm.org/svn/llvm-project/llvm/trunk/utils/vim/syntax/llvm.vim // For VIM regex syntax, see: http://vimdoc.sourceforge.net/htmldoc/pattern.html -export function definition() { +export function definition(): monaco.languages.IMonarchLanguage { return { // llvmType types: [ diff --git a/static/modes/mlir-mode.ts b/static/modes/mlir-mode.ts index 0078d61fbc6..f18da751a8a 100644 --- a/static/modes/mlir-mode.ts +++ b/static/modes/mlir-mode.ts @@ -29,6 +29,6 @@ import {definition} from './llvm-ir-mode'; // TODO: write an actual MLIR monaco mode monaco.languages.register({id: 'mlir'}); -monaco.languages.setMonarchTokensProvider('mlir', definition() as any); +monaco.languages.setMonarchTokensProvider('mlir', definition()); export {}; diff --git a/static/multifile-service.ts b/static/multifile-service.ts index a9d6493b783..bf144ca6401 100644 --- a/static/multifile-service.ts +++ b/static/multifile-service.ts @@ -26,6 +26,7 @@ import _ from 'underscore'; import path from 'path-browserify'; import JSZip from 'jszip'; import {Hub} from './hub'; +import {unwrap} from './assert'; const languages = require('./options').options.languages; export interface MultifileFile { @@ -148,8 +149,7 @@ export class MultifileService { return; } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - let content = await zip.file(zipEntry.name)!.async('string'); + let content = await unwrap(zip.file(zipEntry.name)).async('string'); if (content.length > this.maxFilesize) { return; } diff --git a/static/options.interfaces.ts b/static/options.interfaces.ts index f7df9457fab..6be53f7e84b 100644 --- a/static/options.interfaces.ts +++ b/static/options.interfaces.ts @@ -62,4 +62,7 @@ export type Options = { sentryEnvironment?: string; compileOptions: Record; tools: Record>; + supportsExecute: boolean; + supportsLibraryCodeFilter: boolean; + cvCompilerCountMax: number; }; diff --git a/static/options.ts b/static/options.ts index 02f0c48e203..77ee4450d2d 100644 --- a/static/options.ts +++ b/static/options.ts @@ -22,14 +22,16 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. +import {unwrap} from './assert'; + const configElement = document.getElementById('config'); if (!configElement) { throw new Error('Could not find config element in DOM tree'); } // httpRoot & staticRoot are always a string and always set. -window.httpRoot = configElement.getAttribute('httpRoot') as string; -window.staticRoot = configElement.getAttribute('staticRoot') as string; +window.httpRoot = unwrap(configElement.getAttribute('httpRoot')); +window.staticRoot = unwrap(configElement.getAttribute('staticRoot')); const extraOptions: object = JSON.parse(decodeURIComponent(configElement.getAttribute('extraOptions') ?? '"%7B%7D"')); // Encoded {} for (const key in extraOptions) { diff --git a/static/panes/ast-view.ts b/static/panes/ast-view.ts index fa99086def8..c7f702af772 100644 --- a/static/panes/ast-view.ts +++ b/static/panes/ast-view.ts @@ -35,6 +35,7 @@ import * as monacoConfig from '../monaco-config'; import {ga} from '../analytics'; import {Hub} from '../hub'; +import {unwrap} from '../assert'; type DecorationEntry = { linkedCode: any[]; @@ -148,7 +149,7 @@ export class Ast extends MonacoPane { return { text: x, - } as AstCodeEntry; + }; }); } else { this.astCode = results; diff --git a/static/panes/cfg-view.ts b/static/panes/cfg-view.ts index 203283b768d..b4e91fab3d1 100644 --- a/static/panes/cfg-view.ts +++ b/static/panes/cfg-view.ts @@ -153,14 +153,12 @@ export class Cfg extends Pane { dropdownParent: 'body', plugins: ['dropdown_input'], sortField: 'title', - onChange: e => { - this.selectFunction(e as unknown as string); - }, + onChange: e => this.selectFunction(e as unknown as string), }); this.state = state; // This is a workaround for a chrome render bug that's existed since at least 2013 // https://github.com/compiler-explorer/compiler-explorer/issues/4421 - this.extraTransforms = navigator.userAgent.indexOf('AppleWebKit') === -1 ? '' : ' translateZ(0)'; + this.extraTransforms = !navigator.userAgent.includes('AppleWebKit') ? '' : ' translateZ(0)'; } override getInitialHTML() { diff --git a/static/panes/compiler.ts b/static/panes/compiler.ts index 7f67fad9f54..ca406179d51 100644 --- a/static/panes/compiler.ts +++ b/static/panes/compiler.ts @@ -63,7 +63,10 @@ import * as utils from '../utils'; import * as Sentry from '@sentry/browser'; import {editor} from 'monaco-editor'; import IEditorMouseEvent = editor.IEditorMouseEvent; -import {ArtifactType, Tool} from '../../types/tool.interfaces'; +import {Tool, ArtifactType} from '../../types/tool.interfaces'; +import {assert, unwrap, unwrapString} from '../assert'; +import {CompilerOutputOptions} from '../../types/features/filters.interfaces'; +import {AssemblyDocumentationInstructionSet} from '../../types/features/assembly-documentation.interfaces'; const toolIcons = require.context('../../views/resources/logos', false, /\.(png|svg)$/); @@ -118,7 +121,7 @@ type LinkedCode = { }; }; -type Decorations = Record; +type Decorations = Record; type CompileRequestOptions = { userArguments: string; @@ -171,6 +174,8 @@ type Assembly = { source?: { line: number; column?: number; + file?: string | null; + mainsource?: any; }; address?: number; opcodes?: string[]; @@ -413,7 +418,7 @@ export class Compiler extends MonacoPane { @@ -491,7 +492,7 @@ export class Compiler extends MonacoPane { return Components.getOptViewWith( this.id, - this.source as unknown as number, + this.source, this.lastResult?.optOutput, this.getCompilerName(), this.sourceEditorId ?? 0, @@ -510,7 +511,7 @@ export class Compiler extends MonacoPane { return Components.getPpViewWith( this.id, - this.source as unknown as number, + this.source, this.lastResult?.ppOutput, this.getCompilerName(), this.sourceEditorId ?? 0, @@ -521,7 +522,7 @@ export class Compiler extends MonacoPane { return Components.getAstViewWith( this.id, - this.source as unknown as number, + this.source, this.lastResult?.astOutput, this.getCompilerName(), this.sourceEditorId ?? 0, @@ -989,7 +990,7 @@ export class Compiler extends MonacoPane 0) { - this.editor.restoreViewState(this.revealJumpStack.pop() as any); + this.editor.restoreViewState(this.revealJumpStack.pop() ?? null); this.revealJumpStackHasElementsCtxKey.set(this.revealJumpStack.length > 0); } } @@ -1129,7 +1130,7 @@ export class Compiler extends MonacoPane { + getEffectiveFilters(): Partial { if (!this.compiler) return {}; const filters = this.filters.get(); if (filters.binaryObject && !this.compiler.supportsBinaryObject) { @@ -1145,7 +1146,7 @@ export class Compiler extends MonacoPane { + this.compiler.disabledFilters.forEach(filter => { if (filters[filter]) { delete filters[filter]; } @@ -1477,7 +1478,7 @@ export class Compiler extends MonacoPane { - (this.decorations.labelUsages as monaco.editor.IModelDeltaDecoration[]).push({ + this.decorations.labelUsages.push({ range: new monaco.Range(line + 1, label.range.startCol, line + 1, label.range.endCol), options: { inlineClassName: 'asm-label-link', @@ -1620,7 +1621,7 @@ export class Compiler extends MonacoPane { const button = argumentButton; - const curOptions = this.optionsField.val(); - if (curOptions && (curOptions as string | string[]).length > 0) { + const curOptions = unwrapString(this.optionsField.val()); + if (curOptions && curOptions.length > 0) { this.optionsField.val(curOptions + ' ' + button.data('arg')); } else { this.optionsField.val(button.data('arg')); @@ -2765,7 +2764,7 @@ export class Compiler extends MonacoPane { if (id === this.id) { - this.eventHub.emit('filtersChange', this.id, this.getEffectiveFilters() as any); + this.eventHub.emit('filtersChange', this.id, this.getEffectiveFilters()); } }); this.eventHub.on('requestCompiler', id => { @@ -2805,7 +2804,7 @@ export class Compiler extends MonacoPane { - this.onOptionsChange($(e.target).val() as string); + this.onOptionsChange(unwrapString($(e.target).val())); }, 800); this.optionsField.on('change', optionsChange).on('keyup', optionsChange); @@ -2959,7 +2958,7 @@ export class Compiler extends MonacoPane, scheme: string): void { - const asmColours = {} as Record>; + const asmColours: Record> = {}; this.assembly.forEach((x, index) => { if (x.source && x.source.line > 0) { const editorId = this.getEditorIdBySourcefile(x.source); @@ -3060,9 +3059,9 @@ export class Compiler extends MonacoPane { - const cacheName = 'asm/' + (instructionSet ? instructionSet + '/' : '') + opcode; + private async getAsmInfo( + opcode: string, + instructionSet: AssemblyDocumentationInstructionSet + ): Promise { + const cacheName = `asm/${instructionSet}/${opcode}`; const cached = OpcodeCache.get(cacheName); if (cached) { if (cached.found) return cached.data; throw new Error(cached.data); } - const response = await getAssemblyDocumentation({opcode: opcode, instructionSet: instructionSet as any}); + const response = await getAssemblyDocumentation({opcode, instructionSet}); const body = await response.json(); if (response.status === 200) { OpcodeCache.set(cacheName, {found: true, data: body}); @@ -3405,18 +3407,20 @@ export class Compiler extends MonacoPane { - ed.focus(); - ed.setPosition(pos); - }); - } else { - this.alertSystem.notify('This token was not found in the documentation. Sorry!', { - group: 'notokenindocs', - alertClass: 'notification-error', - dismissTime: 5000, - }); + if (this.compiler?.supportsAsmDocs) { + const asmHelp = await this.getAsmInfo( + word.word, + this.compiler.instructionSet as AssemblyDocumentationInstructionSet + ); + if (asmHelp) { + this.alertSystem.alert(opcode + ' help', asmHelp.html + appendInfo(asmHelp.url), () => { + ed.focus(); + ed.setPosition(pos); + }); + } else { + this.alertSystem.notify('This token was not found in the documentation. Sorry!', { + group: 'notokenindocs', + alertClass: 'notification-error', + dismissTime: 5000, + }); + } } } catch (error) { this.alertSystem.notify('There was an error fetching the documentation for this opcode (' + error + ').', { @@ -3572,14 +3586,14 @@ export class Compiler extends MonacoPane { - return this.compilerService.getCompilersForLang(this.currentLangId ?? ''); + return this.compilerService.getCompilersForLang(this.currentLangId ?? '') ?? {}; } updateCompilersSelector(info: {options?: string}) { diff --git a/static/panes/conformance-view.ts b/static/panes/conformance-view.ts index e3d151bd09c..2fa0d84fc6f 100644 --- a/static/panes/conformance-view.ts +++ b/static/panes/conformance-view.ts @@ -88,7 +88,7 @@ export class Conformance extends Pane { constructor(hub: Hub, container: Container, state: PaneState & ConformanceViewState) { super(hub, container, state); this.compilerService = hub.compilerService; - this.maxCompilations = (options.cvCompilerCountMax as number) || 6; + this.maxCompilations = options.cvCompilerCountMax; this.langId = state.langId || _.keys(options.languages)[0]; this.source = state.source ?? ''; this.sourceNeedsExpanding = true; @@ -222,7 +222,7 @@ export class Conformance extends Pane { // Compiler id which is being used compilerId: '', // Options which are in use - options: (options.compileOptions as any)[this.langId], + options: options.compileOptions[this.langId], }; } const newSelector = this.selectorTemplate.clone(); @@ -287,7 +287,7 @@ export class Conformance extends Pane { const getCompilerConfig = () => { return Components.getCompilerWith( this.compilerInfo.editorId ?? 0, - undefined as any, + undefined, newCompilerEntry.optionsField?.val(), newCompilerEntry.picker?.lastCompilerId ?? '', this.langId, @@ -518,11 +518,7 @@ export class Conformance extends Pane { let first = true; compilers.map(compiler => { if (compiler) { - const filteredLibraries = LibUtils.getSupportedLibraries( - compiler.libsArr, - langId, - (compiler as any).remote - ); + const filteredLibraries = LibUtils.getSupportedLibraries(compiler.libsArr, langId, compiler.remote); if (first) { libraries = _.extend({}, filteredLibraries); @@ -540,7 +536,7 @@ export class Conformance extends Pane { lib.versions = _.pick(lib.versions, (version, versionkey) => { return versionsInCommon.includes(versionkey); - }) as Record; + }) as Record; // TODO(jeremy-rifkin) } else { libraries[libKey] = false; } @@ -548,12 +544,12 @@ export class Conformance extends Pane { libraries = _.omit(libraries, lib => { return !lib || _.isEmpty(lib.versions); - }) as Record; + }) as Record; // TODO(jeremy-rifkin) } } }); - return libraries as CompilerLibs; + return libraries as CompilerLibs; // TODO(jeremy-rifkin) } getCurrentCompilersIds() { diff --git a/static/panes/device-view.ts b/static/panes/device-view.ts index ee1d3691979..939dd2d804e 100644 --- a/static/panes/device-view.ts +++ b/static/panes/device-view.ts @@ -37,6 +37,7 @@ import {MonacoPaneState} from './pane.interfaces'; import {CompilerInfo} from '../../types/compiler.interfaces'; import {CompilationResult} from '../../types/compilation/compilation.interfaces'; import {ResultLine} from '../../types/resultline/resultline.interfaces'; +import {assert} from '../assert'; export class DeviceAsm extends MonacoPane { private decorations: Record<'linkedCode', monaco.editor.IModelDeltaDecoration[]>; @@ -127,7 +128,8 @@ export class DeviceAsm extends MonacoPane 0) { this.selectedDevice = deviceNames[0]; selectize.setValue(this.selectedDevice, true); - } else if (this.selectedDevice && deviceNames.indexOf(this.selectedDevice) === -1) { + } else if (this.selectedDevice && !deviceNames.includes(this.selectedDevice)) { selectize.clear(true); this.showDeviceAsmResults([{text: ''}]); } else { diff --git a/static/panes/diff.ts b/static/panes/diff.ts index 370a8c541c2..d35a0788106 100644 --- a/static/panes/diff.ts +++ b/static/panes/diff.ts @@ -156,7 +156,7 @@ export class Diff extends MonacoPane{ option: (item, escape) => { diff --git a/static/panes/editor.ts b/static/panes/editor.ts index ebf028b10dc..64d7a4b9ba6 100644 --- a/static/panes/editor.ts +++ b/static/panes/editor.ts @@ -54,9 +54,10 @@ import {Decoration, Motd} from '../motd.interfaces'; import type {escape_html} from 'tom-select/dist/types/utils'; import ICursorSelectionChangedEvent = editor.ICursorSelectionChangedEvent; import {Compiler} from './compiler'; +import {assert, unwrap} from '../assert'; const loadSave = new loadSaveLib.LoadSave(); -const languages = options.languages as Record; +const languages = options.languages; type ResultLineWithSourcePane = ResultLine & { sourcePane: string; @@ -77,7 +78,7 @@ export class Editor extends MonacoPane; + private editorSourceByLang: Partial>; private alertSystem: Alert; private filename: string | false; private awaitingInitialResults: boolean; @@ -91,7 +92,7 @@ export class Editor extends MonacoPane; private mouseMoveThrottledFunction?: ((e: monaco.editor.IEditorMouseEvent) => void) & _.Cancelable; - private cursorSelectionThrottledFunction?: (e: monaco.editor.ICursorSelectionChangedEvent) => void & _.Cancelable; + private cursorSelectionThrottledFunction?: (e: monaco.editor.ICursorSelectionChangedEvent) => void; private vimMode: any; private vimFlag: JQuery; private loadSaveButton: JQuery; @@ -168,7 +169,7 @@ export class Editor extends MonacoPane; + this.editorSourceByLang = {}; this.awaitingInitialResults = false; @@ -205,7 +206,7 @@ export class Editor extends MonacoPane void & _.Cancelable, - 500 - ); + this.cursorSelectionThrottledFunction = _.throttle(this.onDidChangeCursorSelection.bind(this), 500); this.editor.onDidChangeCursorSelection(e => { if (this.cursorSelectionThrottledFunction) this.cursorSelectionThrottledFunction(e); }); @@ -548,23 +544,25 @@ export class Editor extends MonacoPane { - return this.hub.compilerService.compilersByLang[language?.id ?? '']; + return this.hub.compilerService.getCompilersForLang(language.id); }); this.languageInfoButton = this.domRoot.find('.language-info'); this.languageInfoButton.popover({}); this.languageBtn = this.domRoot.find('.change-language'); - this.selectize = new TomSelect(this.languageBtn as any, { + const changeLanguageButton = this.languageBtn[0]; + assert(changeLanguageButton instanceof HTMLSelectElement); + this.selectize = new TomSelect(changeLanguageButton, { sortField: 'name', valueField: 'id', labelField: 'name', searchField: ['name'], placeholder: '🔍 Select a language...', - options: _.map(usableLanguages, _.identity) as any[], + options: [...usableLanguages], items: this.currentLanguage?.id ? [this.currentLanguage.id] : [], dropdownParent: 'body', plugins: ['dropdown_input'], - onChange: _.bind(this.onLanguageChange, this), + onChange: this.onLanguageChange.bind(this) as (x: any) => void, closeAfterSelect: true, render: { option: this.renderSelectizeOption.bind(this), @@ -779,7 +777,7 @@ export class Editor extends MonacoPane { let knownCompiler = false; - const compilerExtInfo = this.hub.compilerService.findCompiler( - this.currentLanguage?.id ?? '', - compiler.compiler + const compilerExtInfo = unwrap( + this.hub.compilerService.findCompiler(this.currentLanguage?.id ?? '', compiler.compiler) ); const semver = this.cleanupSemVer(compilerExtInfo.semver); let groupOrName = compilerExtInfo.baseName || compilerExtInfo.groupName || compilerExtInfo.name; if (semver && groupOrName) { groupOrName = groupOrName.toLowerCase(); - if (groupOrName.indexOf('gcc') !== -1) { + if (groupOrName.includes('gcc')) { quickBenchState.compiler = 'gcc-' + semver; knownCompiler = true; - } else if (groupOrName.indexOf('clang') !== -1) { + } else if (groupOrName.includes('clang')) { quickBenchState.compiler = 'clang-' + semver; knownCompiler = true; } @@ -870,7 +867,7 @@ export class Editor extends MonacoPane { - debouncedEmitChange: ((e: boolean) => void) & Cancelable = (() => {}) as any; + debouncedEmitChange: (e: boolean) => void = () => {}; cursorSelectionThrottledFunction: ((e: any) => void) & Cancelable; lastChangeEmitted: string; constructor(hub: Hub, container: Container, state: FlagsViewState & MonacoPaneState) { diff --git a/static/panes/gccdump-view.ts b/static/panes/gccdump-view.ts index b5be0507a9b..c83962eb702 100644 --- a/static/panes/gccdump-view.ts +++ b/static/panes/gccdump-view.ts @@ -40,6 +40,7 @@ import * as monacoConfig from '../monaco-config'; import {GccDumpFiltersState, GccDumpViewState, GccDumpViewSelectedPass} from './gccdump-view.interfaces'; import {ga} from '../analytics'; +import {assert} from '../assert'; export class GccDump extends MonacoPane { selectize: TomSelect; @@ -150,7 +151,8 @@ export class GccDump extends MonacoPane { linkedFadeTimeoutId: NodeJS.Timeout | null = null; @@ -94,7 +95,7 @@ export class Ir extends MonacoPane if (source !== null && source.file !== null) { this.eventHub.emit( 'editorLinkLine', - this.compilerInfo.editorId as number, + unwrap(this.compilerInfo.editorId), source.line, -1, -1, @@ -196,7 +197,7 @@ export class Ir extends MonacoPane this.eventHub.emit( 'editorLinkLine', - this.compilerInfo.editorId as number, + unwrap(this.compilerInfo.editorId), sourceLine, sourceColumnBegin, sourceColumnEnd, diff --git a/static/panes/llvm-opt-pipeline.ts b/static/panes/llvm-opt-pipeline.ts index 8ca27ee135c..02069cd0f4a 100644 --- a/static/panes/llvm-opt-pipeline.ts +++ b/static/panes/llvm-opt-pipeline.ts @@ -44,6 +44,7 @@ import { LLVMOptPipelineOutput, LLVMOptPipelineResults, } from '../../types/compilation/llvm-opt-pipeline-output.interfaces'; +import {unwrap} from '../assert'; const MIN_SIDEBAR_WIDTH = 100; @@ -84,7 +85,7 @@ export class LLVMOptPipeline extends MonacoPane { state.sidebarWidth = parseInt( - (document.defaultView as Window).getComputedStyle(this.passesColumn.get()[0]).width, + unwrap(document.defaultView).getComputedStyle(this.passesColumn.get()[0]).width, 10 ); state.sidebarWidth = Math.max(state.sidebarWidth, MIN_SIDEBAR_WIDTH); @@ -164,9 +165,10 @@ export class LLVMOptPipeline extends MonacoPane= passes.length) { @@ -345,7 +347,7 @@ export class LLVMOptPipeline extends MonacoPane { const topBarHeight = utils.updateAndCalcTopBarHeight(this.domRoot, this.topBar, this.hideable); - const otherWidth = (this.passesColumn.width() as number) + (this.passesColumnResizer.width() as number); - const domWidth = this.domRoot.width() as number; + const otherWidth = unwrap(this.passesColumn.width()) + unwrap(this.passesColumnResizer.width()); + const domWidth = unwrap(this.domRoot.width()); if (otherWidth > domWidth) { this.passesColumn.get()[0].style.width = domWidth + 'px'; } this.editor.layout({ width: domWidth - otherWidth, - height: (this.domRoot.height() as number) - topBarHeight, + height: unwrap(this.domRoot.height()) - topBarHeight, }); - (this.body.get(0) as HTMLElement).style.height = (this.domRoot.height() as number) - topBarHeight + 'px'; + unwrap(this.body.get(0)).style.height = unwrap(this.domRoot.height()) - topBarHeight + 'px'; }); } diff --git a/static/panes/output.ts b/static/panes/output.ts index 88255173405..43454c7f709 100644 --- a/static/panes/output.ts +++ b/static/panes/output.ts @@ -275,20 +275,20 @@ export class Output extends Pane { add(msg: string, lineNum?: number, column?: number, filename?: string) { const elem = $('
').appendTo(this.contentRoot); if (lineNum) { - elem.html( - $('') - .html(msg) - .on('click', e => { - this.emitEditorLinkLine(lineNum, column, filename, true); - // do not bring user to the top of index.html - // http://stackoverflow.com/questions/3252730 - e.preventDefault(); - return false; - }) - .on('mouseover', () => { - this.emitEditorLinkLine(lineNum, column, filename, false); - }) as any // TODO - ); + elem.empty(); + $('') + .html(msg) + .on('click', e => { + this.emitEditorLinkLine(lineNum, column, filename, true); + // do not bring user to the top of index.html + // http://stackoverflow.com/questions/3252730 + e.preventDefault(); + return false; + }) + .on('mouseover', () => { + this.emitEditorLinkLine(lineNum, column, filename, false); + }) + .appendTo(elem); } else { elem.html(msg); } diff --git a/static/panes/pane.ts b/static/panes/pane.ts index cc4c7fbbe45..a0444919f05 100644 --- a/static/panes/pane.ts +++ b/static/panes/pane.ts @@ -35,6 +35,7 @@ import * as utils from '../utils'; import {PaneRenaming} from '../widgets/pane-renaming'; import {EventHub} from '../event-hub'; import {Hub} from '../hub'; +import {unwrap} from '../assert'; /** * Basic container for a tool pane in Compiler Explorer. @@ -296,8 +297,8 @@ export abstract class MonacoPane extends Pan _.defer(() => { const topBarHeight = utils.updateAndCalcTopBarHeight(this.domRoot, this.topBar, this.hideable); this.editor.layout({ - width: this.domRoot.width() as number, - height: (this.domRoot.height() as number) - topBarHeight, + width: unwrap(this.domRoot.width()), + height: unwrap(this.domRoot.height()) - topBarHeight, }); }); } diff --git a/static/panes/pp-view.ts b/static/panes/pp-view.ts index 0f9d1ba3a39..38f05024bdb 100644 --- a/static/panes/pp-view.ts +++ b/static/panes/pp-view.ts @@ -33,6 +33,7 @@ import {PPViewState} from './pp-view.interfaces'; import {Container} from 'golden-layout'; import {MonacoPaneState} from './pane.interfaces'; import {Hub} from '../hub'; +import {unwrap} from '../assert'; export class PP extends MonacoPane { options: any; @@ -100,10 +101,10 @@ export class PP extends MonacoPane { - return hub.compilerService.compilersByLang[language.id]; + return hub.compilerService.getCompilersForLang(language.id); }); - if (!state.compilerLanguageId) { + if (!(state.compilerLanguageId as any)) { state.compilerLanguageId = this.settings.defaultLanguage ?? 'c++'; } @@ -125,18 +126,17 @@ export class Tree { this.initCallbacks(); this.onSettingsChange(this.settings); - this.selectize = new TomSelect(this.languageBtn[0] as HTMLInputElement, { + assert(this.languageBtn[0] instanceof HTMLInputElement); + this.selectize = new TomSelect(this.languageBtn[0], { sortField: 'name', valueField: 'id', labelField: 'name', searchField: ['name'], - options: usableLanguages as any[], + options: usableLanguages, items: [this.multifileService.getLanguageId()], dropdownParent: 'body', plugins: ['input_autogrow'], - onChange: (val: any) => { - this.onLanguageChange(val as string); - }, + onChange: (val: any) => this.onLanguageChange(val), }); this.onLanguageChange(this.multifileService.getLanguageId()); @@ -162,11 +162,11 @@ export class Tree { } private getCmakeArgs(): string { - return this.cmakeArgsInput.val() as string; + return unwrapString(this.cmakeArgsInput.val()); } private getCustomOutputFilename(): string { - return _.escape(this.customOutputFilenameInput.val() as string); + return _.escape(unwrapString(this.customOutputFilenameInput.val())); } public currentState(): TreeState { @@ -719,10 +719,10 @@ export class Tree { private resize() { utils.updateAndCalcTopBarHeight(this.domRoot, this.topBar, this.hideable); - const mainbarHeight = this.topBar.outerHeight(true) as number; - const argsHeight = this.domRoot.find('.panel-args').outerHeight(true) as number; - const outputfileHeight = this.domRoot.find('.panel-outputfile').outerHeight(true) as number; - const innerHeight = this.domRoot.innerHeight() as number; + const mainbarHeight = unwrap(this.topBar.outerHeight(true)); + const argsHeight = unwrap(this.domRoot.find('.panel-args').outerHeight(true)); + const outputfileHeight = unwrap(this.domRoot.find('.panel-outputfile').outerHeight(true)); + const innerHeight = unwrap(this.domRoot.innerHeight()); this.root.height(innerHeight - mainbarHeight - argsHeight - outputfileHeight); } diff --git a/static/settings.ts b/static/settings.ts index 5a81cd2d81b..da7a55585e0 100644 --- a/static/settings.ts +++ b/static/settings.ts @@ -30,6 +30,8 @@ import {themes, Themes} from './themes'; import {AppTheme, ColourScheme, ColourSchemeInfo} from './colour'; import {Hub} from './hub'; import {EventHub} from './event-hub'; +import {keys} from '../lib/common-utils'; +import {assert, unwrapString} from './assert'; export type FormatBase = 'Google' | 'LLVM' | 'Mozilla' | 'Chromium' | 'WebKit' | 'Microsoft' | 'GNU'; @@ -118,7 +120,7 @@ class NumericSelect extends Select { super(elem, name, populate); } override getUi(): number { - return Number(this.val() as string); + return Number(this.val()); } } @@ -298,8 +300,7 @@ export class Settings { }; // We need theme data to populate the colour schemes; We don't add the selector until later - // keys(themes) is Themes[] but TS does not realize without help - const themesData = (Object.keys(themes) as Themes[]).map((theme: Themes) => { + const themesData = keys(themes).map((theme: Themes) => { return {label: themes[theme].id, desc: themes[theme].name}; }); const defaultThemeId = themes.system.id; @@ -345,7 +346,8 @@ export class Settings { NumericSelect ).elem; defaultFontScaleSelector.on('change', e => { - this.eventHub.emit('broadcastFontScale', parseInt((e.target as HTMLSelectElement).value)); + assert(e.target instanceof HTMLSelectElement); + this.eventHub.emit('broadcastFontScale', parseInt(e.target.value)); }); const formats: FormatBase[] = ['Google', 'LLVM', 'Mozilla', 'Chromium', 'WebKit', 'Microsoft', 'GNU']; @@ -401,19 +403,19 @@ export class Settings { const themeSelect = this.root.find('.theme'); themeSelect.on('change', () => { this.onThemeChange(); - $.data(themeSelect, 'last-theme', themeSelect.val() as string); + $.data(themeSelect, 'last-theme', unwrapString(themeSelect.val())); }); const colourSchemeSelect = this.root.find('.colourScheme'); colourSchemeSelect.on('change', e => { const currentTheme = this.settings.theme; - $.data(themeSelect, 'theme-' + currentTheme, colourSchemeSelect.val() as ColourScheme); + $.data(themeSelect, 'theme-' + currentTheme, unwrapString(colourSchemeSelect.val())); }); const enableAllSchemesCheckbox = this.root.find('.alwaysEnableAllSchemes'); enableAllSchemesCheckbox.on('change', this.onThemeChange.bind(this)); - $.data(themeSelect, 'last-theme', themeSelect.val() as string); + $.data(themeSelect, 'last-theme', unwrapString(themeSelect.val())); this.onThemeChange(); } @@ -454,14 +456,17 @@ export class Settings { const themeSelect = this.root.find('.theme'); const colourSchemeSelect = this.root.find('.colourScheme'); - const oldScheme = colourSchemeSelect.val() as string; - const newTheme = themeSelect.val() as colour.AppTheme; + if (!colourSchemeSelect.val()) { + return; + } + const oldScheme = unwrapString(colourSchemeSelect.val()); + const newTheme = unwrapString(themeSelect.val()); this.fillColourSchemeSelector(colourSchemeSelect, newTheme); const newThemeStoredScheme = $.data(themeSelect, 'theme-' + newTheme) as colour.AppTheme | undefined; // If nothing else, set the new scheme to the first of the available ones - let newScheme = colourSchemeSelect.first().val() as string; + let newScheme = unwrapString(colourSchemeSelect.first().val()); // If we have one old one stored, check if it's still valid and set it if so if (newThemeStoredScheme && this.selectorHasOption(colourSchemeSelect, newThemeStoredScheme)) { newScheme = newThemeStoredScheme; diff --git a/static/widgets/compiler-picker.ts b/static/widgets/compiler-picker.ts index 33f22fa4674..7fedd0b4b84 100644 --- a/static/widgets/compiler-picker.ts +++ b/static/widgets/compiler-picker.ts @@ -43,7 +43,7 @@ export class CompilerPicker { domNode: HTMLSelectElement; eventHub: EventHub; id: number; - compilerService: any; + compilerService: CompilerService; onCompilerChange: (x: string) => any; tomSelect: TomSelect | null; lastLangId: string; @@ -179,7 +179,7 @@ export class CompilerPicker { getOptions(langId: string, compilerId: string) { const favorites = this.getFavorites(); - return (Object.values(this.compilerService.getCompilersForLang(langId)) as any[]) + return Object.values(this.compilerService.getCompilersForLang(langId) ?? {}) .filter(e => (this.compilerIsVisible(e) && !e.hidden) || e.id === compilerId) .map(e => { e.$groups = [e.group]; diff --git a/static/widgets/history-widget.ts b/static/widgets/history-widget.ts index 80dd691135e..e566458aaac 100644 --- a/static/widgets/history-widget.ts +++ b/static/widgets/history-widget.ts @@ -30,6 +30,7 @@ import {editor} from 'monaco-editor'; import IStandaloneDiffEditor = editor.IStandaloneDiffEditor; import ITextModel = editor.ITextModel; +import {unwrap} from '../assert'; export class HistoryDiffState { public model: ITextModel; @@ -110,8 +111,7 @@ export class HistoryWidget { private populateFromLocalStorage() { this.currentList = sortedList(); this.populate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.find('.historiccode'), + unwrap(this.modal).find('.historiccode'), this.currentList.map((data): Entry => { const dt = new Date(data.dt).toString(); const languages = HistoryWidget.getLanguagesFromHistoryEntry(data).join(', '); @@ -128,8 +128,7 @@ export class HistoryWidget { } private hideRadiosAndSetDiff() { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const root = this.modal!.find('.historiccode'); + const root = unwrap(this.modal).find('.historiccode'); const items = root.find('li:not(.template)'); let foundbase = false; @@ -151,16 +150,14 @@ export class HistoryWidget { const itemRight = this.currentList.find(item => item.dt === dt); if (itemRight) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.rhs!.update(itemRight); + unwrap(this.rhs).update(itemRight); } } else if (base.prop('checked')) { foundbase = true; const itemLeft = this.currentList.find(item => item.dt === dt); if (itemLeft) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.lhs!.update(itemLeft); + unwrap(this.lhs).update(itemLeft); } } @@ -217,11 +214,10 @@ export class HistoryWidget { } private resizeLayout() { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const tabcontent = this.modal!.find('div.tab-content'); + const tabcontent = unwrap(this.modal).find('div.tab-content'); this.diffEditor?.layout({ - width: tabcontent.width() as number, - height: (tabcontent.height() as number) - 20, + width: unwrap(tabcontent.width()), + height: unwrap(tabcontent.height()) - 20, }); } @@ -235,10 +231,8 @@ export class HistoryWidget { this.onLoadCallback = onLoad; // It can't tell that we initialize modal on initializeIfNeeded, so it sticks to the possibility of it being null - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.on('shown.bs.modal', () => this.resizeLayout()); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.modal(); + unwrap(this.modal).on('shown.bs.modal', () => this.resizeLayout()); + unwrap(this.modal).modal(); ga.proxy('send', { hitType: 'event', diff --git a/static/widgets/libs-widget.ts b/static/widgets/libs-widget.ts index cc4bd668360..d18c06ca652 100644 --- a/static/widgets/libs-widget.ts +++ b/static/widgets/libs-widget.ts @@ -27,6 +27,7 @@ import {options} from '../options'; import * as local from '../local'; import {Library, LibraryVersion} from '../options.interfaces'; import {Lib, WidgetState} from './libs-widget.interfaces'; +import {unwrapString} from '../assert'; const FAV_LIBS_STORE_KEY = 'favlibs'; @@ -366,7 +367,7 @@ export class LibsWidget { } startSearching() { - const searchText = (this.domRoot.find('.lib-search-input').val() as string).toString(); + const searchText = unwrapString(this.domRoot.find('.lib-search-input').val()); this.clearSearchResults(); diff --git a/static/widgets/load-save.ts b/static/widgets/load-save.ts index eeea642a835..9d2017f2d31 100644 --- a/static/widgets/load-save.ts +++ b/static/widgets/load-save.ts @@ -29,6 +29,7 @@ import {Alert} from './alert'; import {ga} from '../analytics'; import * as local from '../local'; import {Language} from '../../types/languages.interfaces'; +import {unwrap, unwrapString} from '../assert'; const history = require('../history'); @@ -118,8 +119,7 @@ export class LoadSave { private async populateBuiltins() { const builtins = (await this.fetchBuiltins()).filter(entry => this.currentLanguage?.id === entry.lang); return LoadSave.populate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.find('.examples'), + unwrap(this.modal).find('.examples'), builtins.map(elem => { return { name: elem.name, @@ -134,8 +134,7 @@ export class LoadSave { const keys = Object.keys(files); LoadSave.populate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.find('.local-storage'), + unwrap(this.modal).find('.local-storage'), keys.map(name => { const data = files[name]; return { @@ -175,10 +174,8 @@ export class LoadSave { private populateLocalHistory() { LoadSave.populate( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.find('.local-history'), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - history.sources(this.currentLanguage!.id).map(data => { + unwrap(this.modal).find('.local-history'), + history.sources(unwrap(this.currentLanguage).id).map(data => { const dt = new Date(data.dt).toString(); return { name: dt.replace(/\s\(.*\)/, ''), @@ -224,8 +221,7 @@ export class LoadSave { this.setMinimalOptions(editorText, currentLanguage); this.populateLocalHistory(); this.onLoadCallback = onLoad; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.modal!.find('.local-file').attr('accept', currentLanguage.extensions.join(',')); + unwrap(this.modal).find('.local-file').attr('accept', currentLanguage.extensions.join(',')); this.populateBuiltins().then(() => this.modal?.modal()); ga.proxy('send', { hitType: 'event', @@ -235,7 +231,7 @@ export class LoadSave { } private onSaveToBrowserStorage() { - const saveNameValue = this.modal?.find('.save-name').val(); + const saveNameValue = unwrapString(this.modal?.find('.save-name').val()); if (!saveNameValue) { this.alertSystem.alert('Save name', 'Invalid save name'); return; diff --git a/static/widgets/site-templates-widget.ts b/static/widgets/site-templates-widget.ts index f3f6f79caa0..a7c8f810fce 100644 --- a/static/widgets/site-templates-widget.ts +++ b/static/widgets/site-templates-widget.ts @@ -24,6 +24,7 @@ import $ from 'jquery'; import {SiteTemplatesType} from '../../types/features/site-templates.interfaces'; +import {assert, unwrap} from '../assert'; import {Settings} from '../settings'; class SiteTemplatesWidget { @@ -35,15 +36,17 @@ class SiteTemplatesWidget { constructor(siteTemplateScreenshots: any) { this.siteTemplateScreenshots = siteTemplateScreenshots; this.modal = $('#site-template-loader'); - this.img = document.getElementById('site-template-preview') as HTMLImageElement; + const siteTemplatePreview = document.getElementById('site-template-preview'); + assert(siteTemplatePreview instanceof HTMLImageElement); + this.img = siteTemplatePreview; } async getTemplates() { if (this.templatesConfig === null) { - this.templatesConfig = await new Promise((resolve, reject) => { + this.templatesConfig = await new Promise((resolve, reject) => { $.getJSON(window.location.origin + window.httpRoot + 'api/siteTemplates', resolve); }); } - return this.templatesConfig as SiteTemplatesType; + return this.templatesConfig; } getCurrentTheme() { const theme = Settings.getStoredSettings()['theme']; @@ -80,7 +83,7 @@ class SiteTemplatesWidget { li.addEventListener( 'mouseover', () => { - this.img.src = this.getAsset(li_copy.getAttribute('data-name') as string); + this.img.src = this.getAsset(unwrap(li_copy.getAttribute('data-name'))); }, false ); diff --git a/static/widgets/timing-info-widget.ts b/static/widgets/timing-info-widget.ts index 56744bc5c79..6265a87de28 100644 --- a/static/widgets/timing-info-widget.ts +++ b/static/widgets/timing-info-widget.ts @@ -28,6 +28,7 @@ import {Chart, ChartData, defaults} from 'chart.js'; import 'chart.js/auto'; import {CompilationResult} from '../../types/compilation/compilation.interfaces'; import _ from 'underscore'; +import {unwrap} from '../assert'; type Data = ChartData<'bar', number[], string> & {steps: number}; @@ -80,10 +81,10 @@ function initializeChartDataFromResult(compileResult: CompilationResult, totalTi }; if (compileResult.retreivedFromCache) { - pushTimingInfo(data, 'Retrieve result from cache', compileResult.retreivedFromCacheTime as number); + pushTimingInfo(data, 'Retrieve result from cache', unwrap(compileResult.retreivedFromCacheTime)); if (compileResult.packageDownloadAndUnzipTime) { - pushTimingInfo(data, 'Download binary from cache', compileResult.execTime as string | number); + pushTimingInfo(data, 'Download binary from cache', unwrap(compileResult.execTime)); } if (compileResult.execResult && compileResult.execResult.execTime) { @@ -107,7 +108,7 @@ function initializeChartDataFromResult(compileResult: CompilationResult, totalTi if (compileResult.execResult && compileResult.execResult.execTime) { pushTimingInfo(data, 'Execution', compileResult.execResult.execTime); } else { - pushTimingInfo(data, 'Execution', compileResult.execTime as string | number); + pushTimingInfo(data, 'Execution', unwrap(compileResult.execTime)); } } @@ -141,6 +142,9 @@ function displayData(data: Data) { const chartDiv = modal.find('#chart'); chartDiv.html(''); + // eslint thinks "This assertion is unnecessary since it does not change the type of the expression" + // Typescript disagrees. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const canvas = $('') as JQuery; chartDiv.append(canvas); diff --git a/types/compiler.interfaces.ts b/types/compiler.interfaces.ts index 9c25aa596b0..1d856b4dbbe 100644 --- a/types/compiler.interfaces.ts +++ b/types/compiler.interfaces.ts @@ -32,6 +32,9 @@ export type CompilerInfo = { id: string; exe: string; name: string; + version: string; + fullVersion: string; + baseName: string; alias: string[]; options: string; versionFlag?: string; @@ -48,20 +51,36 @@ export type CompilerInfo = { instructionSet: string; needsMulti: boolean; adarts: string; - supportsDeviceAsmView: boolean; - supportsDemangle: boolean; - supportsBinary: boolean; - supportsBinaryObject: boolean; - supportsIntel: boolean; - interpreted: boolean; + supportsDeviceAsmView?: boolean; + supportsDemangle?: boolean; + supportsBinary?: boolean; + supportsBinaryObject?: boolean; + supportsIntel?: boolean; + interpreted?: boolean; // (interpreted || supportsBinary) && supportsExecute - supportsExecute: boolean; + supportsExecute?: boolean; + supportsGccDump?: boolean; + supportsFiltersInBinary?: boolean; + supportsOptOutput?: boolean; + supportsPpView?: boolean; + supportsAstView?: boolean; + supportsIrView?: boolean; + supportsLLVMOptPipelineView?: boolean; + supportsRustMirView?: boolean; + supportsRustMacroExpView?: boolean; + supportsRustHirView?: boolean; + supportsHaskellCoreView?: boolean; + supportsHaskellStgView?: boolean; + supportsHaskellCmmView?: boolean; + supportsCfg?: boolean; + supportsGnatDebugViews?: boolean; + supportsLibraryCodeFilter?: boolean; executionWrapper: string; - supportsLibraryCodeFilter: boolean; postProcess: string[]; lang: string; group: string; groupName: string; + $groups: string[]; includeFlag: string; includePath: string; linkFlag: string; @@ -87,6 +106,8 @@ export type CompilerInfo = { name?: string; preamble?: string; }; + remote: any; + disabledFilters: string[]; }; export interface ICompiler { diff --git a/types/features/assembly-documentation.interfaces.ts b/types/features/assembly-documentation.interfaces.ts index 4d035962393..75ed561b6ac 100644 --- a/types/features/assembly-documentation.interfaces.ts +++ b/types/features/assembly-documentation.interfaces.ts @@ -24,9 +24,19 @@ import {AssemblyInstructionInfo} from '../../lib/asm-docs/base'; +export type AssemblyDocumentationInstructionSet = + | 'amd64' + | 'arm32' + | 'avr' + | 'evm' + | 'java' + | 'llvm' + | 'mos6502' + | 'python'; + export interface AssemblyDocumentationRequest { /** Specifies which instruction set to look for */ - instructionSet: 'amd64' | 'arm32' | 'java'; + instructionSet: AssemblyDocumentationInstructionSet; /** Instruction set opcode to look for */ opcode: string; } diff --git a/types/features/filters.interfaces.ts b/types/features/filters.interfaces.ts index c8d9e0a962e..b9a5763b544 100644 --- a/types/features/filters.interfaces.ts +++ b/types/features/filters.interfaces.ts @@ -26,6 +26,7 @@ // applied to the compiler output. They correspond to the "Compiler output // options" and "Compiler output filters" drop down menu in a compiler pane. +// TODO(jeremy-rifkin): Change name to include "filters"? export type CompilerOutputOptions = { binary: boolean; binaryObject: boolean;