diff --git a/.eslintrc.cjs b/.eslintrc.cjs index dcff71a..b661577 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,13 +1,22 @@ /* eslint-env node */ module.exports = { - ignorePatterns: ["/target/**", "/jest.config.js"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + ignorePatterns: ["/target/**", "/*.js", "/*.cjs"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + ], parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, plugins: ["@typescript-eslint"], root: true, rules: { - // Sometimes you just need a `while(true)`. + // Some silly rules forbidding things that are not wrong: "no-constant-condition": "off", + "no-empty": "off", // Typescript already checks problematic fallthrough. // The eslint rule is a bit dumb and also complains about // obvious clear fallthrough like `case "a": case "b"`. @@ -23,5 +32,29 @@ module.exports = { // `any` is genrally bad, but sometimes it's the nicest solution // Just let me use it without any ceremony. "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + // This needs to be turned off until the AST types are redesigned. + "@typescript-eslint/no-non-null-assertion": "off", + // "value is always truthy" YES IT IS. Typescript already emits errors + // for the important cases here. + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-confusing-void-expression": [ + "error", + { + ignoreArrowShorthand: true, + }, + ], + // No, I will use `type` instead of `interface`. + "@typescript-eslint/consistent-type-definitions": ["error", "type"], + // Useful extra lints that are not on by default: + "@typescript-eslint/explicit-module-boundary-types": "warn", + // This has caused several bugs before. Thanks eslint! + "@typescript-eslint/strict-boolean-expressions": [ + "error", + { + allowNullableObject: true, + allowNullableBoolean: false, + }, + ], }, }; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/package.json b/package.json index bc17568..57e8c98 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "node-dev --respawn src/index.ts", "build": "tsc", "fmt": "prettier -w .", - "test": "jest" + "test": "jest", + "lint": "eslint ." }, "author": "", "license": "ISC", diff --git a/src/ast.ts b/src/ast.ts index 24086fc..52ea3e5 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -460,7 +460,7 @@ export type Folder = { type: FoldFn; }; -const ITEM_DEFAULT: symbol = Symbol("item must not be overriden"); +const ITEM_DEFAULT = Symbol("item must not be overriden"); export const DEFAULT_FOLDER: Folder = { ast() { diff --git a/src/error.ts b/src/error.ts index d171714..9b690ea 100644 --- a/src/error.ts +++ b/src/error.ts @@ -23,7 +23,7 @@ export class CompilerError extends Error { } } -export function withErrorHandler(input: string, f: () => void) { +export function withErrorHandler(input: string, f: () => void): void { try { f(); } catch (e) { diff --git a/src/index.ts b/src/index.ts index 3225dfa..5c05fc4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,12 +44,13 @@ function main() { } if (!isValidIdent(packageName)) { - console.error(`error: package name \`${packageName}\` is not a valid identifer`); + console.error( + `error: package name \`${packageName}\` is not a valid identifer` + ); process.exit(1); } console.log(`package name: '${packageName}'`); - withErrorHandler(input, () => { const start = Date.now(); @@ -92,7 +93,7 @@ function main() { if (error && error.code === 1) { console.log(stderr); } else if (error) { - console.error(`failed to spawn wasm-tools: ${error}`); + console.error(`failed to spawn wasm-tools: ${error.message}`); } else { if (stderr) { console.log(stderr); diff --git a/src/lexer.ts b/src/lexer.ts index 865acc6..1ea29e4 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -36,8 +36,7 @@ export type DatalessToken = | "==" | "<=" | ">=" - | "!=" - | "!"; + | "!="; export type TokenIdent = { kind: "identifier"; ident: string }; diff --git a/src/lower.ts b/src/lower.ts index ca86d4e..d16fde2 100644 --- a/src/lower.ts +++ b/src/lower.ts @@ -441,6 +441,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) { kind = `${valty}.ne`; break; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const instr: wasm.NumericInstr = { kind } as any; // Typescript is buggy. instrs.push(instr); } else if (lhsTy.kind === "bool" && rhsTy.kind === "bool") { @@ -582,11 +583,11 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) { switch (expr.lhs.ty!.kind) { case "tuple": { // Tuples have a by-value ABI, so we can simply index. - const lhsSize = argRetAbi(expr.lhs.ty!).length; + const lhsSize = argRetAbi(expr.lhs.ty).length; const resultAbi = argRetAbi(expr.ty!); const resultSize = resultAbi.length; const wasmIdx = wasmTypeIdxForTupleField( - expr.lhs.ty!, + expr.lhs.ty, expr.field.fieldIdx! ); @@ -625,7 +626,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) { break; } case "if": { - lowerExpr(fcx, instrs, expr.cond!); + lowerExpr(fcx, instrs, expr.cond); fcx.currentBlockDepth++; const thenInstrs: wasm.Instr[] = []; diff --git a/src/printer.ts b/src/printer.ts index adc2205..321e3cd 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -36,7 +36,7 @@ function printItem(item: Item): string { return id + printImportDef(item.node); } case "mod": { - return id +printMod(item.node); + return id + printMod(item.node); } } } diff --git a/src/typeck.ts b/src/typeck.ts index f194c1b..9f9855f 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -444,7 +444,7 @@ export class InferContext { } } - public assign(lhs_: Ty, rhs_: Ty, span: Span) { + public assign(lhs_: Ty, rhs_: Ty, span: Span): void { const lhs = this.resolveIfPossible(lhs_); const rhs = this.resolveIfPossible(rhs_); @@ -586,7 +586,7 @@ export function checkBody( const type: Type | undefined = loweredBindingTy && { ...expr.type!, - ty: loweredBindingTy!, + ty: loweredBindingTy, }; return { @@ -689,7 +689,7 @@ export function checkBody( case "call": { const lhs = this.expr(expr.lhs); lhs.ty = infcx.resolveIfPossible(lhs.ty!); - const lhsTy = lhs.ty!; + const lhsTy = lhs.ty; if (lhsTy.kind !== "fn") { throw new CompilerError( `expression of type ${printTy(lhsTy)} is not callable`, @@ -700,7 +700,7 @@ export function checkBody( const args = expr.args.map((arg) => this.expr(arg)); lhsTy.params.forEach((param, i) => { - if (!args[i]) { + if (args.length <= i) { throw new CompilerError( `missing argument of type ${printTy(param)}`, expr.span @@ -905,7 +905,8 @@ export function checkBody( const resolveTy = (ty: Ty, span: Span) => { const resTy = infcx.resolveIfPossible(ty); - if (!resTy) { + // TODO: When doing deep resolution, we need to check for _any_ vars. + if (resTy.kind === "var") { throw new CompilerError("cannot infer type", span); } return resTy; diff --git a/src/utils.ts b/src/utils.ts index f8be4fc..7392b56 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,7 +3,7 @@ export function encodeUtf8(s: string): Uint8Array { } export class Ids { - nextId: number = 0; + nextId = 0; public next(): number { return this.nextId++; @@ -22,13 +22,13 @@ export function unwrap(value: T | undefined): T { * It uses JSON+string equality instead of refernece equality. */ export class ComplexMap { - inner: Map = new Map(); + inner = new Map(); public get(key: K): V | undefined { return this.inner.get(this.mangleKey(key)); } - public set(key: K, value: V) { + public set(key: K, value: V): void { this.inner.set(this.mangleKey(key), value); } diff --git a/src/wasm/defs.ts b/src/wasm/defs.ts index 806e26b..303ed3f 100644 --- a/src/wasm/defs.ts +++ b/src/wasm/defs.ts @@ -265,6 +265,7 @@ export type ControlInstr = export type Instr = | NumericInstr + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents | VectorInstr | ReferenceInstr | ParametricInstr diff --git a/src/wasm/wat.ts b/src/wasm/wat.ts index cad9eb0..4668726 100644 --- a/src/wasm/wat.ts +++ b/src/wasm/wat.ts @@ -140,8 +140,8 @@ function printString(s: string, f: FmtCtx) { function printBinaryString(buf: Uint8Array, f: FmtCtx) { const parts: string[] = []; - for (let i = 0; i < buf.length; i++) { - const byte = buf[i]; + + buf.forEach((byte) => { const noEscape = (byte > 0x30 && byte <= 0x5a) || (byte > 0x61 && byte <= 0x71); if (noEscape) { @@ -149,13 +149,13 @@ function printBinaryString(buf: Uint8Array, f: FmtCtx) { } else { parts.push(`\\${byte.toString(16).padStart(2, "0")}`); } - } + }); f.word(`"${parts.join("")}"`); } function printId(id: string | undefined, f: FmtCtx) { - if (id) { + if (id !== undefined) { f.word(`$${id}`); } } @@ -215,7 +215,7 @@ function printBlockType(type: Blocktype, f: FmtCtx) { } function printMemarg(arg: MemArg, f: FmtCtx) { - if (arg.offset /*0->false*/) { + if (arg.offset === undefined || arg.offset === 0) { f.word(`offset=${arg.offset}`); } if (arg.align !== undefined) { @@ -437,7 +437,7 @@ function printInstr(instr: Instr, f: FmtCtx) { f.controlFlow(instr.kind); f.word(instr.func); const name = f.mod.funcs[instr.func]?._name; - if (name) { + if (name !== undefined) { f.comment(name); } break;