diff --git a/.eslintrc.yml b/.eslintrc.yml index 6f9cd59a5..dabb954e5 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -147,6 +147,7 @@ rules: unicorn/no-process-exit: off '@typescript-eslint/no-empty-function': off '@typescript-eslint/no-unused-vars': off + '@typescript-eslint/no-explicit-any': off parserOptions: sourceType: module ecmaVersion: 2020 diff --git a/lib/asm-parser-dotnet.ts b/lib/asm-parser-dotnet.ts index 98ae2221e..765287338 100644 --- a/lib/asm-parser-dotnet.ts +++ b/lib/asm-parser-dotnet.ts @@ -42,64 +42,64 @@ export class DotNetAsmParser { for (const line in asmLines) { const trimmedLine = asmLines[line].trim(); - if (!trimmedLine || trimmedLine.startsWith(';')) continue; - if (trimmedLine.endsWith(':')) { - if (trimmedLine.includes('(')) { - methodDef[line] = trimmedLine.substring(0, trimmedLine.length - 1); - allAvailable.push(methodDef[line]); - } - else { - labelDef[line] = { - name: trimmedLine.substring(0, trimmedLine.length - 1), - remove: false, - }; - allAvailable.push(labelDef[line].name); - } - continue; - } - - const labelResult = trimmedLine.matchAll(labelRefRe).next(); - if (!labelResult.done) { - const name = labelResult.value[1]; - const index = asmLines[line].indexOf(name) + 1; - labelUsage[line] = { - name: labelResult.value[1], - range: { startCol: index, endCol: index + name.length }, - }; - usedLabels.push(labelResult.value[1]); - } - - let methodResult = trimmedLine.matchAll(methodRefRe).next(); - if (methodResult.done) methodResult = trimmedLine.matchAll(tailCallRe).next(); - if (!methodResult.done) { - const name = methodResult.value[1]; - const index = asmLines[line].indexOf(name) + 1; - methodUsage[line] = { - name: methodResult.value[1], - range: { startCol: index, endCol: index + name.length }, - }; - } + if (!trimmedLine || trimmedLine.startsWith(';')) continue; + if (trimmedLine.endsWith(':')) { + if (trimmedLine.includes('(')) { + methodDef[line] = trimmedLine.substring(0, trimmedLine.length - 1); + allAvailable.push(methodDef[line]); + } + else { + labelDef[line] = { + name: trimmedLine.substring(0, trimmedLine.length - 1), + remove: false, + }; + allAvailable.push(labelDef[line].name); + } + continue; + } + + const labelResult = trimmedLine.matchAll(labelRefRe).next(); + if (!labelResult.done) { + const name = labelResult.value[1]; + const index = asmLines[line].indexOf(name) + 1; + labelUsage[line] = { + name: labelResult.value[1], + range: { startCol: index, endCol: index + name.length }, + }; + usedLabels.push(labelResult.value[1]); + } + + let methodResult = trimmedLine.matchAll(methodRefRe).next(); + if (methodResult.done) methodResult = trimmedLine.matchAll(tailCallRe).next(); + if (!methodResult.done) { + const name = methodResult.value[1]; + const index = asmLines[line].indexOf(name) + 1; + methodUsage[line] = { + name: methodResult.value[1], + range: { startCol: index, endCol: index + name.length }, + }; + } } if (removeUnused) { for (const line in labelDef) { - if (!usedLabels.includes(labelDef[line].name)) { - labelDef[line].remove = true; - } - } + if (!usedLabels.includes(labelDef[line].name)) { + labelDef[line].remove = true; + } + } } return { - labelDef, - labelUsage, - methodDef, - methodUsage, - allAvailable, + labelDef, + labelUsage, + methodDef, + methodUsage, + allAvailable, }; } - cleanAsm(asmLines: string[]) { - const cleanedAsm = []; + cleanAsm(asmLines: string[]): string[] { + const cleanedAsm: string[] = []; for (const line of asmLines) { if (!line) continue; @@ -116,18 +116,18 @@ export class DotNetAsmParser { if (line.startsWith(';') && !line.startsWith('; Emitting')) continue; cleanedAsm.push(line); - } - - return cleanedAsm; + } + + return cleanedAsm; } process(asmResult: string, filters) { const startTime = process.hrtime.bigint(); - const asm: { + const asm: { text: string, - source: Source | null, - labels: InlineLabel[], + source: Source | null, + labels: InlineLabel[], }[] = []; let labelDefinitions: [string, number][] = []; @@ -143,12 +143,12 @@ export class DotNetAsmParser { for (const i in result.labelDef) { const label = result.labelDef[i]; - labelDefinitions.push([label.name, parseInt(i)]); + labelDefinitions.push([label.name, parseInt(i)]); } for (const i in result.methodDef) { const method = result.methodDef[i]; - labelDefinitions.push([method, parseInt(i)]); + labelDefinitions.push([method, parseInt(i)]); } for (const line in asmLines) { @@ -157,30 +157,30 @@ export class DotNetAsmParser { const labels: InlineLabel[] = []; const label = result.labelUsage[line] || result.methodUsage[line]; if (label) { - if (result.allAvailable.includes(label.name)) { - labels.push(label); - } + if (result.allAvailable.includes(label.name)) { + labels.push(label); + } } - asm.push({ - text: asmLines[line], - source: null, - labels, - }); + asm.push({ + text: asmLines[line], + source: null, + labels, + }); } let lineOffset = 1; labelDefinitions = labelDefinitions.sort((a, b) => a[1] < b[1] ? -1 : 1); for (const index in labelDefinitions) { - if (result.labelDef[labelDefinitions[index][1]] && - result.labelDef[labelDefinitions[index][1]].remove) { - labelDefinitions[index][1] = -1; - lineOffset--; - continue; - } - - labelDefinitions[index][1] += lineOffset; + if (result.labelDef[labelDefinitions[index][1]] && + result.labelDef[labelDefinitions[index][1]].remove) { + labelDefinitions[index][1] = -1; + lineOffset--; + continue; + } + + labelDefinitions[index][1] += lineOffset; } const endTime = process.hrtime.bigint(); diff --git a/lib/compilers/dotnet.ts b/lib/compilers/dotnet.ts index e3225a2ab..40481441f 100644 --- a/lib/compilers/dotnet.ts +++ b/lib/compilers/dotnet.ts @@ -115,7 +115,7 @@ class DotNetCompiler extends BaseCompiler { execOptions.customCwd = programDir; await fs.writeFile(projectFilePath, projectFileContent); - let crossgen2Options = []; + const crossgen2Options: string[] = []; const configurableOptions = this.configurableOptions; for (const configurableOption of configurableOptions) { @@ -123,7 +123,7 @@ class DotNetCompiler extends BaseCompiler { if (optionIndex === -1 || optionIndex === options.length - 1) { continue; } - crossgen2Options = crossgen2Options.concat([options[optionIndex], options[optionIndex + 1]]); + crossgen2Options.push(options[optionIndex], options[optionIndex + 1]); } const configurableSwitches = this.configurableSwitches; diff --git a/lib/utils.ts b/lib/utils.ts index fea13bc6e..9e7732094 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -27,7 +27,8 @@ import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs-extra'; -import quote from 'shell-quote'; +import { ComponentConfig, ItemConfigType } from 'golden-layout'; +import { parse as quoteParse } from 'shell-quote'; import _ from 'underscore'; interface IResultLineTag { @@ -45,12 +46,7 @@ interface IResultLine { const tabsRe = /\t/g; const lineRe = /\r?\n/; -/*** - * - * @param {string} text - * @returns {string[]} - */ -export function splitLines(text) { +export function splitLines(text: string): string[] { if (!text) return []; const result = text.split(lineRe); if (result.length > 0 && result[result.length - 1] === '') @@ -58,30 +54,13 @@ export function splitLines(text) { return result; } -/*** - * @callback eachLineFunc - * @param {string} line - * @returns {*} - */ - -/*** - * - * @param {string} text - * @param {eachLineFunc} func - * @param {*} [context] - */ -export function eachLine(text: string, func, context?): IResultLine[] { - return _.each(splitLines(text), func, context); +export function eachLine(text: string, func: (line: string) => (IResultLine | void)): (IResultLine | void)[] { + return splitLines(text).map(func); } -/*** - * - * @param {string} line - * @returns {string} - */ -export function expandTabs(line) { +export function expandTabs(line: string): string { let extraChars = 0; - return line.replace(tabsRe, function (match, offset) { + return line.replace(tabsRe, (match, offset) => { const total = offset + extraChars; const spacesNeeded = (total + 8) & 7; extraChars += spacesNeeded - 1; @@ -89,7 +68,7 @@ export function expandTabs(line) { }); } -export function maskRootdir(filepath) { +export function maskRootdir(filepath: string): string { if (filepath) { // todo: make this compatible with local installations and windows etc return filepath.replace(/^\/tmp\/compiler-explorer-compiler[\w\d-.]*\//, '/app/').replace(/^\/app\//, ''); @@ -98,23 +77,9 @@ export function maskRootdir(filepath) { } } -/*** - * @typedef {Object} lineTag - * @property {string} text - * @property {number} line - * @property {number} text - */ - -/*** - * @typedef {Object} lineObj - * @property {string} text - * @property {lineTag} [tag] - * @inner - */ - const ansiColoursRe = /\x1B\[[\d;]*[Km]/g; -function _parseOutputLine(line, inputFilename, pathPrefix) { +function _parseOutputLine(line: string, inputFilename?: string, pathPrefix?: string) { line = line.split('').join(''); if (pathPrefix) line = line.replace(pathPrefix, ''); if (inputFilename) { @@ -128,17 +93,10 @@ function _parseOutputLine(line, inputFilename, pathPrefix) { return line; } -/*** - * - * @param lines - * @param inputFilename - * @param pathPrefix - * @returns {lineObj[]} - */ -export function parseOutput(lines, inputFilename, pathPrefix) { +export function parseOutput(lines: string, inputFilename?: string, pathPrefix?: string): IResultLine[] { const re = /^\s*[(:](\d+)(:?,?(\d+):?)?[):]*\s*(.*)/; const reWithFilename = /^\s*([\w.]*)[(:](\d+)(:?,?(\d+):?)?[):]*\s*(.*)/; - const result = []; + const result: IResultLine[] = []; eachLine(lines, line => { line = _parseOutputLine(line, inputFilename, pathPrefix); if (!inputFilename) { @@ -171,16 +129,9 @@ export function parseOutput(lines, inputFilename, pathPrefix) { return result; } -/*** - * - * @param lines - * @param inputFilename - * @param pathPrefix - * @returns {lineObj[]} - */ -export function parseRustOutput(lines, inputFilename, pathPrefix) { +export function parseRustOutput(lines: string, inputFilename?: string, pathPrefix?: string) { const re = /^ --> [(:](\d+)(:?,?(\d+):?)?[):]*\s*(.*)/; - const result = []; + const result: IResultLine[] = []; eachLine(lines, line => { line = _parseOutputLine(line, inputFilename, pathPrefix); if (line !== null) { @@ -192,12 +143,14 @@ export function parseRustOutput(lines, inputFilename, pathPrefix) { const column = parseInt(match[3] || '0'); const previous = result.pop(); - previous.tag = { - line, - column, - text: previous.text.replace(ansiColoursRe, ''), - }; - result.push(previous); + if (previous !== undefined) { + previous.tag = { + line, + column, + text: previous.text.replace(ansiColoursRe, ''), + }; + result.push(previous); + } lineObj.tag = { line, @@ -211,23 +164,12 @@ export function parseRustOutput(lines, inputFilename, pathPrefix) { return result; } -/*** - * - * @param {string} name - * @param {number} len - * @returns {string} - */ -export function padRight(name, len) { +export function padRight(name: string, len: number): string { while (name.length < len) name = name + ' '; return name; } -/*** - * - * @param {string} name - * @returns {string} - */ -export function trimRight(name) { +export function trimRight(name: string): string { let l = name.length; while (l > 0 && name[l - 1] === ' ') l -= 1; return name.substr(0, l); @@ -241,7 +183,7 @@ export function trimRight(name) { * @param {string} ip - IP string, of either type (localhost|IPv4|IPv6) * @returns {string} Anonymized IP */ -export function anonymizeIp(ip) { +export function anonymizeIp(ip: string): string { if (ip.includes('localhost')) { return ip; } else if (ip.includes(':')) { @@ -258,7 +200,7 @@ export function anonymizeIp(ip) { * @param {*} object * @returns {string} */ -function objectToHashableString(object) { +function objectToHashableString(object: any): string { // See https://stackoverflow.com/questions/899574/which-is-best-to-use-typeof-or-instanceof/6625960#6625960 return (typeof (object) === 'string') ? object : JSON.stringify(object); } @@ -273,7 +215,7 @@ const DefaultHash = 'Compiler Explorer Default Version 1'; * @param {string} [HashVersion=DefaultHash] - Hash "version" key * @returns {Buffer} - Hash of object */ -export function getBinaryHash(object, HashVersion = DefaultHash) { +export function getBinaryHash(object: any, HashVersion = DefaultHash): Buffer { return crypto.createHmac('sha256', HashVersion) .update(objectToHashableString(object)) .digest(); @@ -287,45 +229,48 @@ export function getBinaryHash(object, HashVersion = DefaultHash) { * @param {string} [HashVersion=DefaultHash] - Hash "version" key * @returns {string} - Hash of object */ -export function getHash(object, HashVersion = DefaultHash) { +export function getHash(object: any, HashVersion = DefaultHash): string { return crypto.createHmac('sha256', HashVersion) .update(objectToHashableString(object)) .digest('hex'); } -/*** - * @typedef {Object} glEditorMainContent - * @property {string} source - Editor content - * @property {string} language - Editor syntax language - * @inner - */ +interface glEditorMainContent { + // Editor content + source: string; + // Editor syntax language + language: string; +} -/*** - * @typedef {Object} glCompilerMainContent - * @property {string} compiler - Compiler id - * @inner - */ +interface glCompilerMainContent { + // Compiler id + compiler: string; +} -/*** - * @typedef {Object} glContents - * @property {glEditorMainContent[]} editors - * @property {glCompilerMainContent[]} compilers - * @inner - */ +interface glContents { + editors: glEditorMainContent[]; + compilers: glCompilerMainContent[]; +} /*** * Gets every (source, lang) & (compilerId) available * @param {Array} content - GoldenLayout config topmost content field * @returns {glContents} */ -export function glGetMainContents(content) { - const contents = { editors: [], compilers: [] }; +export function glGetMainContents(content: ItemConfigType[] = []): glContents { + const contents: glContents = { editors: [], compilers: [] }; _.each(content, element => { if (element.type === 'component') { - if (element.componentName === 'codeEditor') { - contents.editors.push({ source: element.componentState.source, language: element.componentState.lang }); - } else if (element.componentName === 'compiler') { - contents.compilers.push({ compiler: element.componentState.compiler }); + const component = element as ComponentConfig; + if (component.componentName === 'codeEditor') { + contents.editors.push({ + source: component.componentState.source, + language: component.componentState.lang, + }); + } else if (component.componentName === 'compiler') { + contents.compilers.push({ + compiler: component.componentState.compiler, + }); } } else { const subComponents = glGetMainContents(element.content); @@ -336,14 +281,7 @@ export function glGetMainContents(content) { return contents; } -/*** - * - * @param {string} line - * @param {boolean} [atStart=true] - * @returns {string} - */ -export function squashHorizontalWhitespace(line, atStart) { - if (atStart === undefined) atStart = true; +export function squashHorizontalWhitespace(line: string, atStart = true): string { if (line.trim().length === 0) { return ''; } @@ -356,12 +294,7 @@ export function squashHorizontalWhitespace(line, atStart) { return splat.join(' '); } -/*** - * - * @param {string} prop - * @returns {boolean|number|string} - */ -export function toProperty(prop) { +export function toProperty(prop: string): boolean | number | string { if (prop === 'true' || prop === 'yes') return true; if (prop === 'false' || prop === 'no') return false; if (/^-?(0|[1-9]\d*)$/.test(prop)) return parseInt(prop); @@ -378,7 +311,7 @@ export function toProperty(prop) { * @param {string} newValue * @returns {string} */ -export function replaceAll(line, oldValue, newValue) { +export function replaceAll(line: string, oldValue: string, newValue: string): string { if (oldValue.length === 0) return line; let startPoint = 0; for (; ;) { @@ -399,7 +332,7 @@ const BASE32_ALPHABET = '13456789EGKMPTWYabcdefhjnoqrsvxz'; * @param {Buffer} buffer * @returns {string} */ -export function base32Encode(buffer) { +export function base32Encode(buffer: Buffer): string { let output = ''; // This can grow up to 12 bits let digest = 0; @@ -434,9 +367,10 @@ export function base32Encode(buffer) { return output; } -export function splitArguments(options) { - return _.chain(quote.parse(options || '') - .map(x => typeof (x) === 'string' ? x : x.pattern)) +export function splitArguments(options?: string): string[] { + return _.chain(quoteParse(options || '') + // FIXME: x might not contain a .pattern! + .map((x: any) => typeof (x) === 'string' ? x : x.pattern as string)) .compact() .value(); } @@ -446,11 +380,11 @@ export function splitArguments(options) { */ export const APP_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -export function resolvePathFromAppRoot(...args) { +export function resolvePathFromAppRoot(...args: string[]) { return path.resolve(APP_ROOT, ...args); } -export async function fileExists(filename) { +export async function fileExists(filename: string): Promise { try { const stat = await fs.stat(filename); return stat.isFile(); @@ -459,7 +393,7 @@ export async function fileExists(filename) { } } -export async function dirExists(dir) { +export async function dirExists(dir: string): Promise { try { const stat = await fs.stat(dir); return stat.isDirectory(); @@ -468,7 +402,7 @@ export async function dirExists(dir) { } } -export function countOccurrences(collection, item) { +export function countOccurrences(collection: Iterable, item: T): number { // _.reduce(collection, (total, value) => value === item ? total + 1 : total, 0) would work, but is probably slower let result = 0; for (const element of collection) { diff --git a/package-lock.json b/package-lock.json index 7dc83adc5..d5c16c51a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,9 @@ "devDependencies": { "@babel/preset-typescript": "^7.16.5", "@types/bootstrap": "^5.1.6", + "@types/fs-extra": "^9.0.13", "@types/jquery": "^3.5.10", + "@types/shell-quote": "^1.7.1", "@types/underscore": "^1.11.4", "@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/parser": "^5.8.0", @@ -1755,6 +1757,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/hammerjs": { "version": "2.0.41", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", @@ -1849,6 +1860,12 @@ "@types/node": "*" } }, + "node_modules/@types/shell-quote": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.1.tgz", + "integrity": "sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==", + "dev": true + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -16483,6 +16500,15 @@ "@types/range-parser": "*" } }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/hammerjs": { "version": "2.0.41", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", @@ -16577,6 +16603,12 @@ "@types/node": "*" } }, + "@types/shell-quote": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.1.tgz", + "integrity": "sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==", + "dev": true + }, "@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", diff --git a/package.json b/package.json index 9e6d683a1..3ebe0d012 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,9 @@ "devDependencies": { "@babel/preset-typescript": "^7.16.5", "@types/bootstrap": "^5.1.6", + "@types/fs-extra": "^9.0.13", "@types/jquery": "^3.5.10", + "@types/shell-quote": "^1.7.1", "@types/underscore": "^1.11.4", "@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/parser": "^5.8.0", diff --git a/tsconfig.backend.json b/tsconfig.backend.json index abdafdc52..2149bbf9f 100644 --- a/tsconfig.backend.json +++ b/tsconfig.backend.json @@ -9,6 +9,9 @@ "moduleResolution": "node", /* Code generation */ "outDir": "./out/app", - "typeRoots": ["./typings", "./node_modules/@types"] + "typeRoots": ["./typings", "./node_modules/@types"], + /* Other options */ + "strict": true, + "noImplicitAny": false } }