From 5185d1cb0b5b2c9d1ec143af7273fcfe8b56e078 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 16 May 2024 23:14:08 -0400 Subject: [PATCH] Switch to using type arithmetic (no more codegen!) (#184) --- .gitignore | 5 +- codegen/common.ts | 61 ----- codegen/emit.ts | 75 ------- codegen/genExponent.ts | 14 -- codegen/genTests.ts | 38 ---- codegen/genTypes.ts | 69 ------ codegen/spec.ts | 41 ---- codegen/tsconfig.json | 6 - package.json | 16 +- src/exponent/exponentTypeArithmetic.ts | 23 -- src/exponent/exponentValueArithmetic.ts | 20 -- src/exponent/index.ts | 6 - src/measure/__test__/genericMeasureTests.ts | 2 +- src/measure/__test__/numberMeasureTests.ts | 31 ++- .../__test__/unitValueArithmeticTests.ts | 84 ++----- src/measure/exponentTypeArithmetic.ts | 37 ++++ src/measure/format.ts | 11 +- src/measure/genericMeasure.ts | 65 +++--- src/measure/genericMeasureClass.ts | 54 ++--- src/measure/genericMeasureFactory.ts | 2 +- src/measure/genericMeasureStatic.ts | 33 +-- src/measure/genericMeasureUtils.ts | 21 +- src/measure/numberMeasure.ts | 8 +- src/measure/unitTypeArithmetic.ts | 93 ++------ src/measure/unitValueArithmetic.ts | 69 ++---- src/unit/base.ts | 2 +- src/unit/quantities.ts | 4 +- test/exponents.ts | 35 +++ test/measures.ts | 60 +++++ test/tsconfig.json | 9 + test/types/exponents.ts | 14 -- test/types/index.d.ts | 1 - test/types/measures.ts | 51 ----- test/types/tsconfig.json | 16 -- test/types/tslint.json | 6 - test/types/units.ts | 65 ------ test/types/utils.ts | 1 - test/units.ts | 17 ++ test/utils.ts | 10 + yarn.lock | 208 +----------------- 40 files changed, 334 insertions(+), 1049 deletions(-) delete mode 100644 codegen/common.ts delete mode 100644 codegen/emit.ts delete mode 100644 codegen/genExponent.ts delete mode 100644 codegen/genTests.ts delete mode 100644 codegen/genTypes.ts delete mode 100644 codegen/spec.ts delete mode 100644 codegen/tsconfig.json delete mode 100644 src/exponent/exponentTypeArithmetic.ts delete mode 100644 src/exponent/exponentValueArithmetic.ts delete mode 100644 src/exponent/index.ts create mode 100644 src/measure/exponentTypeArithmetic.ts create mode 100644 test/exponents.ts create mode 100644 test/measures.ts create mode 100644 test/tsconfig.json delete mode 100644 test/types/exponents.ts delete mode 100644 test/types/index.d.ts delete mode 100644 test/types/measures.ts delete mode 100644 test/types/tsconfig.json delete mode 100644 test/types/tslint.json delete mode 100644 test/types/units.ts delete mode 100644 test/types/utils.ts create mode 100644 test/units.ts create mode 100644 test/utils.ts diff --git a/.gitignore b/.gitignore index 8cf4124..f5a4e76 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ yarn-error.log ### Editors .vscode/ -### Generated code -generated/ +*.js +*.js.map +*.d.ts \ No newline at end of file diff --git a/codegen/common.ts b/codegen/common.ts deleted file mode 100644 index c5b94e7..0000000 --- a/codegen/common.ts +++ /dev/null @@ -1,61 +0,0 @@ -export interface CommonSpec { - minExponent: number; - maxExponent: number; -} - -export interface CodeGenSpec extends CommonSpec { - operators: PartialOperatorSpec[]; -} - -export interface PartialOperatorSpec { - fileNamePrefix: string; - uncurriedTypeNamePrefix: string; - curriedTypeNamePrefix: string; - testTypeNamePrefix: string; - specialCases: { [left: number]: string }; - compute: (left: number, right: number) => number; -} - -export interface OperatorSpec extends CommonSpec, PartialOperatorSpec {} - -export interface ExponentSpec { - value: number; - type: string; -} - -export function getExponents({ minExponent, maxExponent }: CommonSpec): ExponentSpec[] { - const exponents: ExponentSpec[] = []; - for (let value = minExponent; value <= maxExponent; value++) { - exponents.push(getExponent(value)); - } - return exponents; -} - -export function getExponent(value: number): ExponentSpec { - return { value, type: `"${value}"` }; -} - -export function isExponent(exponent: number, { minExponent, maxExponent }: CommonSpec): boolean { - return exponent >= minExponent && exponent <= maxExponent && exponent === Math.floor(exponent); -} - -export function genFileHeader(): string[] { - return ["// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", ""]; -} - -export function genImport(symbol: string, source: string): string[] { - return [`import { ${symbol} } from "${source}";`, ""]; -} - -export function genUncurriedTypeName(spec: OperatorSpec, left?: string | number, right?: string | number): string { - const args = left !== undefined && right !== undefined ? `<${left}, ${right}>` : ""; - return `${spec.uncurriedTypeNamePrefix}Exponents${args}`; -} - -export function genExponentName({ value }: ExponentSpec): string { - if (value === 0) { - return "Zero"; - } - const sign = value < 0 ? "Neg" : "Pos"; - return `${sign}${Math.abs(value)}`; -} diff --git a/codegen/emit.ts b/codegen/emit.ts deleted file mode 100644 index 65f3336..0000000 --- a/codegen/emit.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { exists, mkdir, writeFile } from "fs"; -import { OperatorSpec } from "./common"; -import { genExponentType } from "./genExponent"; -import { genOperatorTests } from "./genTests"; -import { genOperatorTypes } from "./genTypes"; -import { codeGenSpec } from "./spec"; - -const DIR = "generated"; -const SRC_PREFIX = `src/exponent/${DIR}`; -const TEST_PREFIX = `test/types/${DIR}`; - -export interface EmitPlan { - path: string; - source: string; -} - -export function emit(callback?: () => void): void { - const emits: EmitPlan[] = [ - { path: `${SRC_PREFIX}/exponent.ts`, source: genExponentType(codeGenSpec) }, - ...getOperatorEmitPlans(SRC_PREFIX, genOperatorTypes), - ...getOperatorEmitPlans(TEST_PREFIX, genOperatorTests), - ]; - prepForEmit(() => { - let index = -1; - const nextEmit = () => { - index++; - const isLastEmit = index === emits.length - 1; - emitFile(emits[index], isLastEmit ? callback : nextEmit); - }; - nextEmit(); - }); -} - -function getOperatorEmitPlans(prefix: string, genSource: (spec: OperatorSpec) => string): EmitPlan[] { - const { operators, ...common } = codeGenSpec; - return operators.map(operator => { - const operatorSpec: OperatorSpec = { ...operator, ...common }; - const { fileNamePrefix } = operator; - return { path: `${prefix}/${fileNamePrefix}.ts`, source: genSource(operatorSpec) }; - }); -} - -function prepForEmit(callback: () => void): void { - makeDirectory(SRC_PREFIX, () => makeDirectory(TEST_PREFIX, callback)); -} - -function makeDirectory(path: string, callback: () => void): void { - exists(path, doesExist => { - if (doesExist) { - return callback(); - } - mkdir(path, err => { - if (err) { - console.error(`There was an error creating directory ${path}`); - } else { - callback(); - } - }); - }); -} - -function emitFile({ path, source }: EmitPlan, callback?: () => void): void { - writeFile(path, source, error => { - if (error) { - console.error(`There was an error writing to ${path}`); - } else { - console.log(`Generated ${path}`); - if (callback) { - callback(); - } - } - }); -} - -emit(); diff --git a/codegen/genExponent.ts b/codegen/genExponent.ts deleted file mode 100644 index af6966c..0000000 --- a/codegen/genExponent.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CommonSpec, genFileHeader, getExponent, getExponents } from "./common"; - -export function genExponentType(spec: CommonSpec): string { - const exponents = getExponents(spec) - .map(exponent => exponent.type) - .join(" | "); - return [ - ...genFileHeader(), - `export type Exponent = ${exponents};`, - `export type MinExponent = ${getExponent(spec.minExponent).type};`, - `export type MaxExponent = ${getExponent(spec.maxExponent).type};`, - "", - ].join("\n"); -} diff --git a/codegen/genTests.ts b/codegen/genTests.ts deleted file mode 100644 index 00f7a6a..0000000 --- a/codegen/genTests.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - ExponentSpec, - genExponentName, - genFileHeader, - genImport, - genUncurriedTypeName, - getExponents, - isExponent, - OperatorSpec, -} from "./common"; - -export function genOperatorTests(spec: OperatorSpec): string { - return [ - ...genFileHeader(), - ...genImport(genUncurriedTypeName(spec), "../../../src/exponent"), - ...genTests(spec), - ].join("\n"); -} - -function genTests(spec: OperatorSpec): string[] { - const lines: string[] = []; - const exponents = getExponents(spec); - for (const left of exponents) { - for (const right of exponents) { - lines.push(genTest(spec, left, right)); - } - } - lines.push(""); - return lines; -} - -function genTest(spec: OperatorSpec, left: ExponentSpec, right: ExponentSpec): string { - const result = spec.compute(left.value, right.value); - const typeName = `${spec.testTypeNamePrefix}Of${genExponentName(left)}And${genExponentName(right)}`; - const testType = `${genUncurriedTypeName(spec, left.type, right.type)}`; - const expectedType = isExponent(result, spec) ? `"${result}"` : "never"; - return `type ${typeName} = ${testType}; // $ExpectType ${expectedType}`; -} diff --git a/codegen/genTypes.ts b/codegen/genTypes.ts deleted file mode 100644 index ad9e205..0000000 --- a/codegen/genTypes.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - ExponentSpec, - genExponentName, - genFileHeader, - genImport, - genUncurriedTypeName, - getExponents, - isExponent, - OperatorSpec, -} from "./common"; - -export function genOperatorTypes(spec: OperatorSpec): string { - const exponents = getExponents(spec); - return [ - ...genFileHeader(), - ...genImport("Exponent", "./exponent"), - ...genUncurriedType(spec), - ...genUncurriedTable(spec, exponents), - ...genAllCurriedTables(spec, exponents), - ].join("\n"); -} - -function genUncurriedType(spec: OperatorSpec): string[] { - const typeName = genUncurriedTypeName(spec, "L extends Exponent", "R extends Exponent"); - const tableName = genUncurriedTableName(spec); - return [`export type ${typeName} = ${tableName}[L][R];`, ""]; -} - -function genUncurriedTable(spec: OperatorSpec, exponents: ExponentSpec[]): string[] { - const name = genUncurriedTableName(spec); - const lines = [`interface ${name} {`]; - for (const left of exponents) { - lines.push(indent(`${left.type}: ${genCurriedTableName(spec, left)};`)); - } - lines.push("}", ""); - return lines; -} - -function genUncurriedTableName(spec: OperatorSpec): string { - return `${spec.uncurriedTypeNamePrefix}Table`; -} - -function genAllCurriedTables(spec: OperatorSpec, exponents: ExponentSpec[]): string[] { - const lines: string[] = []; - for (const left of exponents) { - lines.push(...genCurriedTable(spec, exponents, left)); - } - return lines; -} - -function genCurriedTable(spec: OperatorSpec, exponents: ExponentSpec[], left: ExponentSpec): string[] { - const name = genCurriedTableName(spec, left); - const lines = [`interface ${name} {`]; - for (const right of exponents) { - const result = spec.compute(left.value, right.value); - const value = isExponent(result, spec) ? `"${result}"` : "never"; - lines.push(indent(`${right.type}: ${value};`)); - } - lines.push("}", ""); - return lines; -} - -function genCurriedTableName(spec: OperatorSpec, left: ExponentSpec): string { - return `${spec.curriedTypeNamePrefix}${genExponentName(left)}Table`; -} - -function indent(line: string): string { - return " " + line; -} diff --git a/codegen/spec.ts b/codegen/spec.ts deleted file mode 100644 index 43e876b..0000000 --- a/codegen/spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CodeGenSpec } from "./common"; - -const maxExponent = 5; - -export const codeGenSpec: CodeGenSpec = { - minExponent: -maxExponent, - maxExponent, - operators: [ - { - fileNamePrefix: "addition", - uncurriedTypeNamePrefix: "Add", - curriedTypeNamePrefix: "Add", - testTypeNamePrefix: "Sum", - specialCases: { - 0: "R", - }, - compute: (left, right) => left + right, - }, - { - fileNamePrefix: "multiplication", - uncurriedTypeNamePrefix: "Multiply", - curriedTypeNamePrefix: "MultiplyBy", - testTypeNamePrefix: "Product", - specialCases: { - 0: "0", - 1: "R", - }, - compute: (left, right) => left * right, - }, - { - fileNamePrefix: "division", - uncurriedTypeNamePrefix: "Divide", - curriedTypeNamePrefix: "DividedBy", - testTypeNamePrefix: "Quotient", - specialCases: { - 0: "(R extends 0 ? never : 0)", - }, - compute: (left, right) => left / right, - }, - ], -}; diff --git a/codegen/tsconfig.json b/codegen/tsconfig.json deleted file mode 100644 index 7057e42..0000000 --- a/codegen/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "../dist/codegen" - } -} \ No newline at end of file diff --git a/package.json b/package.json index 7ba3986..1d6048e 100644 --- a/package.json +++ b/package.json @@ -20,22 +20,19 @@ "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", "scripts": { - "build": "npm-run-all -s codegen compile:src", + "build": "tsc -p src", "clean": "rimraf dist docs/build", - "codegen": "npm-run-all -s compile:codegen node:codegen", - "compile:codegen": "tsc -p codegen", "compile:docs": "tsc -p docsgen", - "compile:src": "tsc -p src", "docs": "npm-run-all -s compile:docs node:docs", - "lint:codegen": "eslint --config eslint.config.mjs codegen", "lint:docs": "eslint --config eslint.config.mjs docsgen", "lint:src": "eslint --config eslint.config.mjs src", "lint:dist": "./scripts/check-typings.sh", - "lint:types": "dtslint test/types", - "lint": "npm-run-all -p lint:codegen lint:docs lint:src lint:dist", - "node:codegen": "node dist/codegen/emit", + "lint:test": "eslint --config eslint.config.mjs test", + "lint": "npm-run-all -p lint:docs lint:src lint:dist", "node:docs": "node dist/docsgen/index", - "test": "jest --config jest.config.ts", + "test:src": "jest --config jest.config.ts", + "test:types": "tsc -p test", + "test": "npm-run-all -p test:src test:types", "prepack": "yarn clean && yarn build", "verify": "npm-run-all -s build lint test docs" }, @@ -46,7 +43,6 @@ "@types/node": "^20.12.12", "@types/react-dom": "^16.0.11", "commonmark": "^0.31.0", - "dtslint": "^0.3.0", "eslint": "^9.2.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", diff --git a/src/exponent/exponentTypeArithmetic.ts b/src/exponent/exponentTypeArithmetic.ts deleted file mode 100644 index 9825cd4..0000000 --- a/src/exponent/exponentTypeArithmetic.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AddExponents } from "./generated/addition"; -import { DivideExponents } from "./generated/division"; -import { Exponent, MinExponent } from "./generated/exponent"; -import { MultiplyExponents } from "./generated/multiplication"; - -export type PositiveExponent = Exclude; -type NonNegativeExponent = SubtractExponents; - -export type SubtractExponents = AddExponents>; - -/** Exponents that can be added to N without producing an error. */ -export type AddendOf = SubtractExponents, N>; - -/** Exponents that can be subtracted from N without producing an error. */ -export type SubtrahendOf = AddendOf>; - -/** Exponents that can be multiplied with N without producing an error. */ -export type MultiplicandOf = "0" extends N ? Exponent : DivideExponents>; - -/** Exponents that are non-error multiples of N. */ -export type ProductOf = MultiplyExponents; - -type Negative = MultiplyExponents; diff --git a/src/exponent/exponentValueArithmetic.ts b/src/exponent/exponentValueArithmetic.ts deleted file mode 100644 index 5089aa9..0000000 --- a/src/exponent/exponentValueArithmetic.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Exponent } from "./generated/exponent"; - -export function getExponentValue(value: Exponent): number { - return parseInt(value, 10); -} - -export const negateExponent = (value: Exponent) => toExponent(-getExponentValue(value)); -export const addExponents = wrapBinaryExponentFn((left, right) => left + right); -export const multiplyExponents = wrapBinaryExponentFn((left, right) => left * right); -export const divideExponents = wrapBinaryExponentFn((left, right) => left / right); - -type BinaryExponentFn = (left: Exponent, right: Exponent) => Exponent; - -function wrapBinaryExponentFn(fn: (left: number, right: number) => number): BinaryExponentFn { - return (left, right) => toExponent(fn(getExponentValue(left), getExponentValue(right))); -} - -function toExponent(value: number): Exponent { - return `${value}` as Exponent; -} diff --git a/src/exponent/index.ts b/src/exponent/index.ts deleted file mode 100644 index 6bc036a..0000000 --- a/src/exponent/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./exponentTypeArithmetic"; -export * from "./exponentValueArithmetic"; -export * from "./generated/addition"; -export * from "./generated/division"; -export * from "./generated/exponent"; -export * from "./generated/multiplication"; diff --git a/src/measure/__test__/genericMeasureTests.ts b/src/measure/__test__/genericMeasureTests.ts index 2b5aea6..16890a0 100644 --- a/src/measure/__test__/genericMeasureTests.ts +++ b/src/measure/__test__/genericMeasureTests.ts @@ -41,7 +41,7 @@ describe("Generic measures", () => { sub: (x, y) => x - y, mult: (x, y) => x * y, div: (x, y) => x / y, - pow: (x, y) => x ** y, + reciprocal: x => 1 / x, compare: (x, y) => x - y, format: x => `${x}`, }; diff --git a/src/measure/__test__/numberMeasureTests.ts b/src/measure/__test__/numberMeasureTests.ts index 1c8de9a..173586b 100644 --- a/src/measure/__test__/numberMeasureTests.ts +++ b/src/measure/__test__/numberMeasureTests.ts @@ -10,7 +10,7 @@ describe("Number measures", () => { describe("dimension", () => { it("should create dimensions with value 1", () => { - expect(Measure.dimension("foo", "f")).toEqual({ value: 1, unit: { foo: ["f", "1"] }, symbol: "f" }); + expect(Measure.dimension("foo", "f")).toEqual({ value: 1, unit: { foo: ["f", 1] }, symbol: "f" }); }); }); @@ -68,10 +68,6 @@ describe("Number measures", () => { expect(Measure.abs(Measure.of(-10, mps))).toEqual(Measure.of(10, mps)); }); - it("cbrt", () => { - expect(Measure.cbrt(Measure.of(64, seconds.cubed()))).toEqual(Measure.of(4, seconds)); - }); - it("ceil", () => { expect(Measure.ceil(Measure.of(3.4, mps))).toEqual(Measure.of(4, mps)); }); @@ -96,18 +92,10 @@ describe("Number measures", () => { ); }); - it("pow", () => { - expect(Measure.pow(Measure.of(3, meters), "4")).toEqual(Measure.of(81, meters.toThe("4"))); - }); - it("round", () => { expect(Measure.round(Measure.of(7.8, mps))).toEqual(Measure.of(8, mps)); }); - it("sqrt", () => { - expect(Measure.sqrt(Measure.of(25, meters.squared()))).toEqual(Measure.of(5, meters)); - }); - it("sum", () => { expect(Measure.sum(Measure.of(10, mps), Measure.of(5, mps), Measure.of(15, mps))).toEqual( Measure.of(30, mps), @@ -161,8 +149,6 @@ describe("Number measures", () => { expect(value.inverse()).toEqual(Measure.of(0.1, meters.inverse())); expect(value.reciprocal()).toEqual(Measure.of(0.1, meters.inverse())); - expect(value.toThe("0")).toEqual(Measure.dimensionless(1)); - expect(value.toThe("1")).toEqual(Measure.of(10, meters)); expect(value.squared()).toEqual(Measure.of(100, meters.squared())); expect(value.cubed()).toEqual(Measure.of(1000, meters.cubed())); }); @@ -257,8 +243,8 @@ describe("Number measures", () => { it("should format units with only negative exponents", () => { expectFormat(seconds.inverse(), "1 s^-1"); - expectFormat(seconds.toThe("-2"), "1 s^-2"); - expectFormat(seconds.toThe("-2").times(meters.toThe("-3")), "1 m^-3 * s^-2"); + expectFormat(seconds.squared().inverse(), "1 s^-2"); + expectFormat(seconds.squared().inverse().over(meters.cubed()), "1 m^-3 * s^-2"); }); it("should format units with positive exponents and one negative exponent", () => { @@ -355,5 +341,16 @@ describe("Number measures", () => { expect(original).not.toBe(copy); expect(original).toEqual(copy); }); + + it("should perform unsafe mappings", () => { + const original = Measure.of(1, meters); + const valueMapped = original.unsafeMap(value => value + 1); + const unitMapped = original.unsafeMap( + value => value + 1, + unit => ({ ...unit, time: ["s", -1] }), + ); + expect(valueMapped).toEqual(Measure.of(2, meters)); + expect(unitMapped).toEqual(Measure.of(2, meters.per(seconds))); + }); }); }); diff --git a/src/measure/__test__/unitValueArithmeticTests.ts b/src/measure/__test__/unitValueArithmeticTests.ts index 869c776..2e680df 100644 --- a/src/measure/__test__/unitValueArithmeticTests.ts +++ b/src/measure/__test__/unitValueArithmeticTests.ts @@ -1,12 +1,11 @@ -import { Exponent } from "../../exponent"; import { Unit, UnitWithSymbols } from "../unitTypeArithmetic"; -import { dimension, divideUnits, exponentiateUnit, multiplyUnits, nthRootUnit } from "../unitValueArithmetic"; +import { dimension, divideUnits, multiplyUnits, reciprocalUnit } from "../unitValueArithmetic"; describe("Unit value arithmetic", () => { function addSymbols(unit: U): UnitWithSymbols { const result: UnitWithSymbols = {}; for (const dimension in unit) { - const exponent: Exponent | undefined = unit[dimension]; + const exponent = unit[dimension]; result[dimension] = exponent === undefined ? undefined : [dimension, exponent]; } return result as any; @@ -17,90 +16,55 @@ describe("Unit value arithmetic", () => { describe("bases", () => { it("should construct base units", () => { - expect(dimension("x")).toEqual(addSymbols({ x: "1" })); + expect(dimension("x")).toEqual(addSymbols({ x: 1 })); }); }); describe("multiplication", () => { it("should multiply two different base units correctly", () => { - expect(multiplyUnits(x, y)).toEqual(addSymbols({ x: "1", y: "1" })); + expect(multiplyUnits(x, y)).toEqual(addSymbols({ x: 1, y: 1 })); }); it("should multiply two of the same base unit correctly", () => { - expect(multiplyUnits(x, x)).toEqual(addSymbols({ x: "2" })); + expect(multiplyUnits(x, x)).toEqual(addSymbols({ x: 2 })); }); it("should multiply complex units correctly", () => { - const left = addSymbols({ x: "1", y: "-2" }); - const right = addSymbols({ y: "1", z: "2" }); - expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: "1", y: "-1", z: "2" })); + const left = addSymbols({ x: 1, y: -2 }); + const right = addSymbols({ y: 1, z: 2 }); + expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: 1, y: -1, z: 2 })); }); it("should remove zero exponents from the result", () => { - const left = addSymbols({ x: "1", y: "2", z: "3" }); - const right = addSymbols({ x: "-1", y: "-2", z: "-3" }); + const left = addSymbols({ x: 1, y: 2, z: 3 }); + const right = addSymbols({ x: -1, y: -2, z: -3 }); expect(multiplyUnits(left, right)).toEqual({}); }); it("should handle explicitly undefined and 0 exponents", () => { - const left = addSymbols({ w: "0", x: "2", y: undefined }); - const right = addSymbols({ x: undefined, y: "0", z: undefined }); - expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: "2" })); + const left = addSymbols({ w: 0, x: 2, y: undefined }); + const right = addSymbols({ x: undefined, y: 0, z: undefined }); + expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: 2 })); }); }); - describe("division", () => { - it("should correctly divide units", () => { - const left = addSymbols({ x: "2", y: "2" }); - const right = addSymbols({ x: "2", y: "-1", z: "2" }); - expect(divideUnits(left, right)).toEqual(addSymbols({ y: "3", z: "-2" })); - }); - }); - - describe("exponentiation", () => { - it("should square a simple unit", () => { - expect(exponentiateUnit(x, "2")).toEqual(addSymbols({ x: "2" })); - }); - - it("should cube a simple unit", () => { - expect(exponentiateUnit(x, "3")).toEqual(addSymbols({ x: "3" })); - }); - - it("should square a complex unit", () => { - expect(exponentiateUnit(addSymbols({ x: "1", y: "-2" }), "2")).toEqual(addSymbols({ x: "2", y: "-4" })); - }); - - it("should invert a unit", () => { - expect(exponentiateUnit(addSymbols({ x: "-1", y: "2", z: "-3" }), "-1")).toEqual( - addSymbols({ x: "1", y: "-2", z: "3" }), - ); - }); - - it("should return the same unit when raised to the one", () => { - const input = addSymbols({ x: "-1", y: "2", z: "-3" }); - expect(exponentiateUnit(input, "1")).toEqual(input); - }); - - it("should return a dimensionless unit when raised to the zero", () => { - expect(exponentiateUnit(addSymbols({ x: "-1", y: "2", z: "-3" }), "0")).toEqual({}); + describe("reciprocals", () => { + it("should get the reciprocal of a unit", () => { + const unit = addSymbols({ x: 1, y: -2, z: 3 }); + expect(reciprocalUnit(unit)).toEqual(addSymbols({ x: -1, y: 2, z: -3 })); }); it("should handle explicitly undefined and 0 exponents", () => { - expect(exponentiateUnit(addSymbols({ x: "2", y: undefined, z: "0" }), "2")).toEqual(addSymbols({ x: "4" })); + const unit = addSymbols({ w: 0, x: 2, y: undefined }); + expect(reciprocalUnit(unit)).toEqual(addSymbols({ x: -2 })); }); }); - describe("roots", () => { - it("should square root the unit", () => { - expect(nthRootUnit(addSymbols({ x: "4", y: "-2" }), "2")).toEqual(addSymbols({ x: "2", y: "-1" })); - }); - - it("should cube root the unit", () => { - expect(nthRootUnit(addSymbols({ x: "3", y: "-3" }), "3")).toEqual(addSymbols({ x: "1", y: "-1" })); - }); - - it("should handle explicitly undefined and 0 exponents", () => { - expect(nthRootUnit(addSymbols({ x: "2", y: undefined, z: "0" }), "2")).toEqual(addSymbols({ x: "1" })); + describe("division", () => { + it("should correctly divide units", () => { + const left = addSymbols({ x: 2, y: 2 }); + const right = addSymbols({ x: 2, y: -1, z: 2 }); + expect(divideUnits(left, right)).toEqual(addSymbols({ y: 3, z: -2 })); }); }); }); diff --git a/src/measure/exponentTypeArithmetic.ts b/src/measure/exponentTypeArithmetic.ts new file mode 100644 index 0000000..411efc2 --- /dev/null +++ b/src/measure/exponentTypeArithmetic.ts @@ -0,0 +1,37 @@ +export type Negative = N extends number + ? `${N}` extends `-${infer Pos extends number}` + ? Pos + : `-${N}` extends `${infer Neg extends number}` + ? Neg + : 0 + : never; + +export type AddIntegers< + Left extends number, + Right extends number, +> = `${Left}` extends `-${infer LeftAbs extends number}` + ? `${Right}` extends `-${infer RightAbs extends number}` + ? Negative> + : Negative> + : `${Right}` extends `-${infer RightAbs extends number}` + ? SubtractPositiveIntegers + : AddPositiveIntegers; + +type AddPositiveIntegers = CoerceNumber< + [...TupleOfSize, ...TupleOfSize]["length"] +>; + +export type SubtractIntegers = AddIntegers>; + +type SubtractPositiveIntegers = + TupleOfSize extends [...infer Diff, ...TupleOfSize] + ? Diff["length"] + : TupleOfSize extends [...infer Diff, ...TupleOfSize] + ? Negative + : 0; + +type CoerceNumber = N extends number ? N : 0; + +type TupleOfSize = []> = BuiltTuple["length"] extends N + ? BuiltTuple + : TupleOfSize; diff --git a/src/measure/format.ts b/src/measure/format.ts index e66ec8e..2677adf 100644 --- a/src/measure/format.ts +++ b/src/measure/format.ts @@ -1,4 +1,3 @@ -import { getExponentValue, negateExponent } from "../exponent/exponentValueArithmetic"; import { SymbolAndExponent, UnitWithSymbols } from "./unitTypeArithmetic"; export function defaultFormatUnit(unit: UnitWithSymbols): string { @@ -11,8 +10,8 @@ export function defaultFormatUnit(unit: UnitWithSymbols): string { return ""; } - const positive = dimensions.filter(([_, dim]) => getExponentValue(dim) > 0); - const negative = dimensions.filter(([_, dim]) => getExponentValue(dim) < 0); + const positive = dimensions.filter(([_, dim]) => dim > 0); + const negative = dimensions.filter(([_, dim]) => dim < 0); if (positive.length === 0) { return formatDimensions(negative); @@ -28,7 +27,7 @@ export function defaultFormatUnit(unit: UnitWithSymbols): string { } function isDimensionPresent(dimension: SymbolAndExponent | undefined): dimension is SymbolAndExponent { - return dimension !== undefined && dimension[1] !== "0"; + return dimension !== undefined && dimension[1] !== 0; } function orderDimensions([leftSymbol]: SymbolAndExponent, [rightSymbol]: SymbolAndExponent): number { @@ -38,14 +37,14 @@ function orderDimensions([leftSymbol]: SymbolAndExponent, [rightSymbol]: SymbolA function formatDimensions(dimensions: SymbolAndExponent[]): string { return dimensions .map(([symbol, exponent]) => { - const exponentStr = exponent !== "1" ? `^${exponent}` : ""; + const exponentStr = exponent !== 1 ? `^${exponent}` : ""; return `${symbol}${exponentStr}`; }) .join(" * "); } function negateDimension([symbol, exponent]: SymbolAndExponent): SymbolAndExponent { - return [symbol, negateExponent(exponent)]; + return [symbol, -exponent]; } function maybeParenthesize(text: string, parenthesize: boolean): string { diff --git a/src/measure/genericMeasure.ts b/src/measure/genericMeasure.ts index cea5637..aeb38ae 100644 --- a/src/measure/genericMeasure.ts +++ b/src/measure/genericMeasure.ts @@ -1,10 +1,9 @@ import { - AllowedExponents, + CubeUnit, DivideUnits, - DivisorUnit, - ExponentiateUnit, - MultiplicandUnit, MultiplyUnits, + ReciprocalUnit, + SquareUnit, Unit, UnitWithSymbols, } from "./unitTypeArithmetic"; @@ -28,8 +27,8 @@ export interface NumericOperations { mult(left: N, right: N): N; /** Returns the quotient of two numbers of type N */ div(left: N, right: N): N; - /** Returns the base raised to the exponent for numbers of type N */ - pow(base: N, exponent: number): N; + /** Returns the reciprocal of the given number of type N */ + reciprocal(value: N): N; /** Compares two numbers returning a negative, zero, or positive value. */ compare(left: N, right: N): number; /** Formats a number for display */ @@ -45,28 +44,6 @@ export interface GenericMeasure { /** The symbol of the unit this measure represents (e.g. 0.3048 m = 1 ft) */ readonly symbol: string | undefined; - /** - * If this measure can be squared, squares it. If this measure is not squarable (due to exponent limitations), then - * this function will have type `never`. - * @returns this measure multiplied by itself - */ - squared: "2" extends AllowedExponents ? () => GenericMeasure> : never; - - /** - * If this measure can be cubed, cubes it. If this measure cannot be cubed (due to exponent limitations), then - * this function will have type `never`. - * @returns this cube of this measure - */ - cubed: "3" extends AllowedExponents ? () => GenericMeasure> : never; - - /** - * Raises this measure to a given power. If the result would give exponents outside of the allowable bounds, this - * will return `never`. - * @param exponent the exponent to raise this measure to - * @returns this exponent to the given power - */ - toThe>(exponent: E): GenericMeasure>; - /** * Adds this measure to another measure with the same unit. * @param other the value to add @@ -97,42 +74,54 @@ export interface GenericMeasure { /** * Multiplies this measure with another measure. * @param other the value to multiply - * @returns the product measure with a unit thats the product of the units + * @returns the product measure with a unit that's the product of the units */ - times>(other: GenericMeasure): GenericMeasure>; + times(other: GenericMeasure): GenericMeasure>; /** * Divides this measure by another measure. * @param other the divisor - * @returns the quotient measure with a unit thats the quotient of the units + * @returns the quotient measure with a unit that's the quotient of the units */ - over>(other: GenericMeasure): GenericMeasure>; + over(other: GenericMeasure): GenericMeasure>; /** * Divides this measure by another measure. * @param other the divisor - * @returns the quotient measure with a unit thats the quotient of the units + * @returns the quotient measure with a unit that's the quotient of the units */ - per>(other: GenericMeasure): GenericMeasure>; + per(other: GenericMeasure): GenericMeasure>; /** * Divides this measure by another measure. * @param other the divisor - * @returns the quotient measure with a unit thats the quotient of the units + * @returns the quotient measure with a unit that's the quotient of the units + */ + div(other: GenericMeasure): GenericMeasure>; + + /** + * Squares the measure. + * @returns this measure multiplied by itself + */ + squared(): GenericMeasure>; + + /** + * Cubes the measure. + * @returns this cube of this measure with a unit that's the cube of the unit */ - div>(other: GenericMeasure): GenericMeasure>; + cubed(): GenericMeasure>; /** * Returns the reciprocal of this measure. * @returns the reciprocal of this measure with a recriprocal unit */ - inverse(): GenericMeasure>; + inverse(): GenericMeasure>; /** * Returns the reciprocal of this measure. * @returns the reciprocal of this measure with a recriprocal unit */ - reciprocal(): GenericMeasure>; + reciprocal(): GenericMeasure>; /** * Maps the value and possibly unit of this measure. diff --git a/src/measure/genericMeasureClass.ts b/src/measure/genericMeasureClass.ts index 98b2f02..3982a07 100644 --- a/src/measure/genericMeasureClass.ts +++ b/src/measure/genericMeasureClass.ts @@ -1,17 +1,15 @@ -import { getExponentValue } from "../exponent/exponentValueArithmetic"; import { defaultFormatUnit } from "./format"; import { GenericMeasure, MeasureFormatter, NumericOperations } from "./genericMeasure"; import { - AllowedExponents, DivideUnits, - DivisorUnit, - ExponentiateUnit, - MultiplicandUnit, MultiplyUnits, + ReciprocalUnit, + SquareUnit, Unit, + CubeUnit, UnitWithSymbols, } from "./unitTypeArithmetic"; -import { divideUnits, exponentiateUnit, multiplyUnits } from "./unitValueArithmetic"; +import { divideUnits, multiplyUnits, reciprocalUnit } from "./unitValueArithmetic"; type GenericMeasureConstructor = new ( value: N, @@ -36,8 +34,6 @@ export function createMeasureClass(num: NumericOperations): GenericMeasure class Measure implements GenericMeasure { public readonly symbol: string | undefined; - public squared!: "2" extends AllowedExponents ? () => GenericMeasure> : never; - public cubed!: "3" extends AllowedExponents ? () => GenericMeasure> : never; constructor( public readonly value: N, @@ -65,43 +61,43 @@ export function createMeasureClass(num: NumericOperations): GenericMeasure return new Measure(num.mult(this.value, value), this.unit); } - public times>( - other: GenericMeasure, - ): GenericMeasure> { - // HACKHACK Need to cast as any to get around excessively deep type instantiation error - return new Measure(num.mult(this.value, other.value), multiplyUnits(this.unit, other.unit)) as any; + public times(other: GenericMeasure): GenericMeasure> { + return new Measure(num.mult(this.value, other.value), multiplyUnits(this.unit, other.unit)); } - public over>(other: GenericMeasure): GenericMeasure> { - // HACKHACK Need to cast as any to get around excessively deep type instantiation error - return new Measure(num.div(this.value, other.value), divideUnits(this.unit, other.unit)) as any; + public over(other: GenericMeasure): GenericMeasure> { + return new Measure(num.div(this.value, other.value), divideUnits(this.unit, other.unit)); } - public per>(other: GenericMeasure): GenericMeasure> { + public per(other: GenericMeasure): GenericMeasure> { return this.over(other); } - public div>(other: GenericMeasure): GenericMeasure> { + public div(other: GenericMeasure): GenericMeasure> { return this.over(other); } - public toThe>(power: E): GenericMeasure> { - return new Measure(num.pow(this.value, getExponentValue(power)), exponentiateUnit(this.unit, power) as any); + public squared(): GenericMeasure> { + return this.times(this as GenericMeasure); } - public inverse(): GenericMeasure> { - return this.toThe("-1"); + public cubed(): GenericMeasure> { + return this.squared().times(this as GenericMeasure); } - public reciprocal(): GenericMeasure> { - return this.toThe("-1"); + public inverse(): GenericMeasure> { + return this.reciprocal(); + } + + public reciprocal(): GenericMeasure> { + return new Measure(num.reciprocal(this.value), reciprocalUnit(this.unit)); } public unsafeMap( valueMap: (value: N) => N, unitMap?: (unit: UnitWithSymbols) => UnitWithSymbols, ): GenericMeasure { - const newUnit = unitMap === undefined ? this.unit : unitMap(this.unit); + const newUnit = unitMap?.(this.unit) ?? this.unit; return new Measure(valueMap(this.value), newUnit as unknown as UnitWithSymbols); } @@ -160,13 +156,5 @@ export function createMeasureClass(num: NumericOperations): GenericMeasure } } - Measure.prototype.squared = function (): any { - return this.toThe("2"); - }; - - Measure.prototype.cubed = function (): any { - return this.toThe("3"); - }; - return Measure; } diff --git a/src/measure/genericMeasureFactory.ts b/src/measure/genericMeasureFactory.ts index f399d09..2d63544 100644 --- a/src/measure/genericMeasureFactory.ts +++ b/src/measure/genericMeasureFactory.ts @@ -6,7 +6,7 @@ import { Unit } from "./unitTypeArithmetic"; import { dimension } from "./unitValueArithmetic"; type DimensionResult = - true extends IsSingleStringLiteral ? GenericMeasure : never; + true extends IsSingleStringLiteral ? GenericMeasure : never; /** The functions needed to construct a measure of a given numeric type */ interface GenericMeasureFactory { diff --git a/src/measure/genericMeasureStatic.ts b/src/measure/genericMeasureStatic.ts index d3247e6..aaebcad 100644 --- a/src/measure/genericMeasureStatic.ts +++ b/src/measure/genericMeasureStatic.ts @@ -1,14 +1,6 @@ import { GenericMeasure } from "./genericMeasure"; import { BinaryFn, PrefixFn, SpreadFn } from "./genericMeasureUtils"; -import { - AllowedExponents, - DivideUnits, - DivisorUnit, - ExponentiateUnit, - MultiplicandUnit, - MultiplyUnits, - Unit, -} from "./unitTypeArithmetic"; +import { DivideUnits, MultiplyUnits, Unit } from "./unitTypeArithmetic"; export interface GenericMeasureStatic { /** Sums a list of one or more measures, all of the same unit. */ @@ -27,22 +19,16 @@ export interface GenericMeasureStatic { subtract: BinaryFn; /** Static version of `left.times(right)` */ - multiply>( - left: GenericMeasure, - right: GenericMeasure, - ): GenericMeasure>; + multiply( + left: GenericMeasure, + right: GenericMeasure, + ): GenericMeasure>; /** Static version of `left.div(right)` */ - divide>( - left: GenericMeasure, - right: GenericMeasure, - ): GenericMeasure>; - - /** Static version of `value.toThe(exp)` */ - pow>( - value: GenericMeasure, - exp: E, - ): GenericMeasure>; + divide( + left: GenericMeasure, + right: GenericMeasure, + ): GenericMeasure>; /** * Creates a function that takes a measure and applies a symbol to its prefix and scales it by a given multiplier. @@ -61,7 +47,6 @@ export const getGenericMeasureStaticMethods = (): GenericMeasureStatic => subtract: (left, right) => left.minus(right), multiply: (left, right) => left.times(right), divide: (left, right) => left.over(right), - pow: (value, exp) => value.toThe(exp), prefix: (prefix, multiplier) => { return measure => { const { symbol } = measure; diff --git a/src/measure/genericMeasureUtils.ts b/src/measure/genericMeasureUtils.ts index 514ac7b..8a6ef1b 100644 --- a/src/measure/genericMeasureUtils.ts +++ b/src/measure/genericMeasureUtils.ts @@ -1,7 +1,5 @@ -import { PositiveExponent } from "../exponent"; import { GenericMeasure } from "./genericMeasure"; -import { NthRootUnit, RadicandUnit, Unit } from "./unitTypeArithmetic"; -import { nthRootUnit } from "./unitValueArithmetic"; +import { Unit } from "./unitTypeArithmetic"; /** A function which applies a symbol prefix and multiplier to a given measure. */ export type PrefixFn = { @@ -13,11 +11,6 @@ export type UnaryFn = { (x: GenericMeasure): GenericMeasure; }; -/** A function which takes the Rth root of a measure's value and unit. */ -export type NthRootFn = { - >(x: GenericMeasure): GenericMeasure>; -}; - /** A function which transforms two measures with same unit into a single measure with the same unit. */ export type BinaryFn = { (left: GenericMeasure, right: GenericMeasure): GenericMeasure; @@ -39,18 +32,6 @@ export function wrapUnaryFn(fn: (x: N) => N): UnaryFn { return x => x.unsafeMap(fn); } -/** - * Converts a function that takes the nth root of a number type (for a specific n) into a function of measures. The `n` - * parameter must be a constant which matches the root that the function takes (e.g. 2 for square root, 3 for cube - * root). - * @param nthRoot a function that takes a specific root of a numeric type - * @param n a compile time constant specifying which nth root the first parameter performs - * @returns a function of measures which takes the nth root of the value and the unit. - */ -export function wrapRootFn(nthRoot: (x: N) => N, n: R): NthRootFn { - return x => x.unsafeMap(nthRoot, unit => nthRootUnit(unit, n)); -} - /** * Converts a binary function of unitless numbers into a function of measures. This assumes that the underlying * operation makes no change to the unit of the measure. For example, this would be an incorrect usage: diff --git a/src/measure/numberMeasure.ts b/src/measure/numberMeasure.ts index 779dd3a..b06e21a 100644 --- a/src/measure/numberMeasure.ts +++ b/src/measure/numberMeasure.ts @@ -1,6 +1,6 @@ import { GenericMeasure, NumericOperations } from "./genericMeasure"; import { createMeasureType, GenericMeasureType } from "./genericMeasureFactory"; -import { NthRootFn, SpreadFn, UnaryFn, wrapRootFn, wrapSpreadFn, wrapUnaryFn } from "./genericMeasureUtils"; +import { SpreadFn, UnaryFn, wrapSpreadFn, wrapUnaryFn } from "./genericMeasureUtils"; import { Unit } from "./unitTypeArithmetic"; interface MeasureStaticMethods { @@ -11,8 +11,6 @@ interface MeasureStaticMethods { round: UnaryFn; trunc: UnaryFn; hypot: SpreadFn; - sqrt: NthRootFn<"2">; - cbrt: NthRootFn<"3">; } const staticMethods: MeasureStaticMethods = { @@ -23,8 +21,6 @@ const staticMethods: MeasureStaticMethods = { round: wrapUnaryFn(Math.round), trunc: wrapUnaryFn(Math.trunc), hypot: wrapSpreadFn(Math.hypot), - sqrt: wrapRootFn(Math.sqrt, "2"), - cbrt: wrapRootFn(Math.cbrt, "3"), }; const numericOps: NumericOperations = { @@ -34,7 +30,7 @@ const numericOps: NumericOperations = { sub: (x, y) => x - y, mult: (x, y) => x * y, div: (x, y) => x / y, - pow: (x, y) => x ** y, + reciprocal: x => 1 / x, compare: (x, y) => x - y, format: x => `${x}`, }; diff --git a/src/measure/unitTypeArithmetic.ts b/src/measure/unitTypeArithmetic.ts index c8d0688..85ff463 100644 --- a/src/measure/unitTypeArithmetic.ts +++ b/src/measure/unitTypeArithmetic.ts @@ -1,89 +1,34 @@ -import { - AddendOf, - AddExponents, - DivideExponents, - Exponent, - MultiplicandOf, - MultiplyExponents, - PositiveExponent, - ProductOf, - SubtractExponents, - SubtrahendOf, -} from "../exponent"; +import { AddIntegers, Negative, SubtractIntegers } from "./exponentTypeArithmetic"; export type Unit = { - [dimension: string]: Exponent | undefined; + [dimension: string]: number | undefined; }; export type UnitWithSymbols = { [D in keyof U]+?: [string, NonNullable] }; -export type SymbolAndExponent = [string, Exponent]; +export type SymbolAndExponent = [string, number]; -// Multiplication - -/** Returns the product of two units. This is the sum of two dimension vectors. */ -export type MultiplyUnits = CleanUnit<{ - [Dim in keyof L | keyof R]: AddExponents>, CoerceExponent>>; +export type MultiplyUnits = StripZeros<{ + [Dimension in keyof Left | keyof Right]: AddIntegers, GetExponent>; }>; -/** A type that is assignable from all units that can be multiplied by U without producing an error. */ -export type MultiplicandUnit = Partial<{ [D in keyof U]: AddendOf> }> & Unit; - -// Division +export type SquareUnit = MultiplyUnits; +export type CubeUnit = MultiplyUnits, U>; -/** Returns the quotient of two units. This is the difference of two dimension vectors. */ -export type DivideUnits> = CleanUnit<{ - [Dim in keyof L | keyof R]: SubtractExponents< - CoerceExponent>, - CoerceExponent> +export type DivideUnits = StripZeros<{ + [Dimension in keyof Left | keyof Right]: SubtractIntegers< + GetExponent, + GetExponent >; }>; -/** A type that is assignable from all units that U can be divided by without producing an error. */ -export type DivisorUnit = Partial<{ [D in keyof U]: SubtrahendOf> }> & Unit; - -// Exponentiation - -/** Returns the unit raised to a power. This is the scalar multiple of the dimension vector. */ -export type ExponentiateUnit = "0" extends N - ? {} - : { [Dim in keyof U]: MultiplyExponents>, N> }; - -/** Returns the union of exponents to which a given unit is allowed to be raised. */ -export type AllowedExponents = Exclude> | "-1" | "0" | "1"; - -/** Returns the union of exponents that raising and exponent to would produce an error. */ -type NonAllowedExponents = { - [Dim in keyof U]: undefined extends U[Dim] ? never : Exclude>>; -}[keyof U]; - -// Roots - -/** Returns the nth root of a unit. This is the inverse scalar multiple of the dimension vector. */ -export type NthRootUnit, N extends PositiveExponent> = 1 extends N - ? U - : { [Dim in keyof U]: DivideExponents>, N> }; - -/** A type that is assignable from all units whose Nth root does not produce an error. */ -export type RadicandUnit = { - [dimension: string]: ProductOf | undefined; -}; - -// Utility types - -/** Makes a unit pretty in intellisense views. */ -// `ExponentiateUnit` is a noop that seems to accomplish this but is slow to compile and we should see if -// there's a workaround. -type CleanUnit = ExponentiateUnit, "1">; - -/** Removes all zero exponent dimensions from a dimension vector */ -type StripZeroes = { [Dim in NonZeroKeys]: U[Dim] }; - -/** Gets the union of all dimensions of a unit with non zero or null exponents */ -type NonZeroKeys = { [Dim in keyof U]: NonNullable extends "0" ? never : Dim }[keyof U]; +export type ReciprocalUnit = StripZeros<{ + [Dimension in keyof U]: Negative>; +}>; -/** Get the exponent at a given dimension of a unit, or 0 if that dimension is undefined */ -type GetExponent = D extends keyof U ? NonNullable : "0"; +type GetExponent = D extends keyof U ? NonNullable : 0; -type CleanExponent = undefined extends E ? "0" : NonNullable; +export type StripZeros = Identity<{ + [K in keyof U as 0 extends U[K] ? never : K]: U[K]; +}>; -type CoerceExponent = E extends Exponent ? E : "0"; +type Identity = { [K in keyof U]: U[K] }; diff --git a/src/measure/unitValueArithmetic.ts b/src/measure/unitValueArithmetic.ts index ce37e46..819a346 100644 --- a/src/measure/unitValueArithmetic.ts +++ b/src/measure/unitValueArithmetic.ts @@ -1,31 +1,24 @@ -import { Exponent, PositiveExponent } from "../exponent"; -import { addExponents, divideExponents, multiplyExponents } from "../exponent/exponentValueArithmetic"; import { - AllowedExponents, DivideUnits, - DivisorUnit, - ExponentiateUnit, - MultiplicandUnit, MultiplyUnits, - NthRootUnit, - RadicandUnit, + ReciprocalUnit, SymbolAndExponent, Unit, UnitWithSymbols, } from "./unitTypeArithmetic"; -export function dimension(dim: Dim, symbol?: string): UnitWithSymbols<{ [D in Dim]: "1" }> { - return { [dim]: [symbol || dim, "1"] } as any; +export function dimension(dim: Dim, symbol?: string): UnitWithSymbols<{ [D in Dim]: 1 }> { + return { [dim]: [symbol ?? dim, 1] } as any; } -export function multiplyUnits>( - left: UnitWithSymbols, - right: UnitWithSymbols, -): UnitWithSymbols> { +export function multiplyUnits( + left: UnitWithSymbols, + right: UnitWithSymbols, +): UnitWithSymbols> { const result: UnitWithSymbols = {}; for (const dimension in left) { const symbolAndExponent = copySymbolAndExponent(left, dimension); - if (symbolAndExponent !== undefined && symbolAndExponent[1] !== "0") { + if (symbolAndExponent !== undefined && symbolAndExponent[1] !== 0) { result[dimension] = symbolAndExponent; } } @@ -37,17 +30,24 @@ export function multiplyUnits>( const [, exponent] = symbolAndExponent; const resultValue: SymbolAndExponent | undefined = result[dimension]; if (resultValue !== undefined) { - const newExponent = addExponents(resultValue[1], exponent); - if (newExponent === "0") { + const newExponent = resultValue[1] + exponent; + if (newExponent === 0) { delete result[dimension]; } else { resultValue[1] = newExponent; } - } else if (exponent !== "0") { + } else if (exponent !== 0) { result[dimension] = symbolAndExponent; } } - return result as any; + return result as UnitWithSymbols>; +} + +export function divideUnits( + left: UnitWithSymbols, + right: UnitWithSymbols, +): UnitWithSymbols> { + return multiplyUnits(left, reciprocalUnit(right)) as unknown as UnitWithSymbols>; } function copySymbolAndExponent(unit: UnitWithSymbols, dimension: string): SymbolAndExponent | undefined { @@ -59,29 +59,7 @@ function copySymbolAndExponent(unit: UnitWithSymbols, dimension: string): Symbol return [symbol, exponent]; } -export function divideUnits>( - left: UnitWithSymbols, - right: UnitWithSymbols, -): UnitWithSymbols> { - const rightInverse = exponentiateUnit(right, "-1") as any; - return multiplyUnits(left, rightInverse) as any; -} - -export function exponentiateUnit>( - unit: UnitWithSymbols, - power: N, -): UnitWithSymbols> { - return expAndRootImpl(unit, exponent => multiplyExponents(exponent, power)); -} - -export function nthRootUnit, N extends PositiveExponent>( - unit: UnitWithSymbols, - root: N, -): UnitWithSymbols> { - return expAndRootImpl(unit, exponent => divideExponents(exponent, root)); -} - -function expAndRootImpl(unit: UnitWithSymbols, updateExponent: (exp: Exponent) => Exponent): any { +export function reciprocalUnit(unit: UnitWithSymbols): UnitWithSymbols> { const result: UnitWithSymbols = {}; for (const dimension in unit) { const symbolAndExponent = unit[dimension]; @@ -89,10 +67,9 @@ function expAndRootImpl(unit: UnitWithSymbols, updateExponent: (exp: Exponent) = continue; } const [symbol, exponent] = symbolAndExponent; - const newExponent = updateExponent(exponent); - if (newExponent !== "0") { - result[dimension] = [symbol, newExponent]; + if (exponent !== 0) { + result[dimension] = [symbol, -exponent]; } } - return result; + return result as UnitWithSymbols>; } diff --git a/src/unit/base.ts b/src/unit/base.ts index 7fb780d..635192b 100644 --- a/src/unit/base.ts +++ b/src/unit/base.ts @@ -15,7 +15,7 @@ interface BaseUnitsMap { bits: "memory"; } -export type BaseUnits = { [U in keyof BaseUnitsMap]: GenericMeasure }; +export type BaseUnits = { [U in keyof BaseUnitsMap]: GenericMeasure }; export const createBaseUnits = (MeasureType: GenericMeasureType): BaseUnits => ({ meters: MeasureType.dimension("length", "m"), diff --git a/src/unit/quantities.ts b/src/unit/quantities.ts index bec7f6e..34a6b6f 100644 --- a/src/unit/quantities.ts +++ b/src/unit/quantities.ts @@ -60,11 +60,11 @@ export const Frequency = Time.inverse(); /** 1 / s² */ export type FrequencyDrift = LiftMeasure; -export const FrequencyDrift = Time.toThe("-2"); +export const FrequencyDrift = Time.inverse().squared(); /** 1 / m² */ export type FuelEfficiency = LiftMeasure; -export const FuelEfficiency = Length.toThe("-2"); +export const FuelEfficiency = Length.inverse().squared(); /** 1 / m */ export type Wavenumber = LiftMeasure; diff --git a/test/exponents.ts b/test/exponents.ts new file mode 100644 index 0000000..be47443 --- /dev/null +++ b/test/exponents.ts @@ -0,0 +1,35 @@ +import { Equal, Expect } from "utils"; +import { AddIntegers, SubtractIntegers } from "../src/measure/exponentTypeArithmetic"; + +export type TestCases = [ + // Addition + Expect, 4>>, + Expect, 4>>, + Expect, 7>>, + + Expect, -2>>, + Expect, 1>>, + Expect, -4>>, + + Expect, -2>>, + Expect, 1>>, + Expect, -4>>, + + Expect, -7>>, + Expect, -5>>, + + // Subtraction + Expect, 5>>, + Expect, -5>>, + Expect, 0>>, + Expect, 2>>, + Expect, -2>>, + + Expect, -5>>, + Expect, -8>>, + + Expect, 8>>, + + Expect, -2>>, + Expect, 7>>, +]; diff --git a/test/measures.ts b/test/measures.ts new file mode 100644 index 0000000..9b4f7b4 --- /dev/null +++ b/test/measures.ts @@ -0,0 +1,60 @@ +import { + Acceleration, + Area, + GenericMeasure, + kilograms, + Length, + Measure, + meters, + newtons, + seconds, + Volume, +} from "safe-units"; +import { value, expectTrue } from "utils"; + +// Valid usages + +expectTrue(value(Measure.dimension("x")).hasType>()); + +expectTrue(value(meters).hasType>()); +expectTrue(value(seconds).hasType>()); +expectTrue(value(newtons).hasType>()); + +const a = newtons.over(kilograms); +expectTrue(value(a).hasType>()); +const accel: Acceleration = a; +expectTrue(value(accel).hasType>()); + +const absement = meters.times(seconds); +expectTrue(value(absement).hasType>()); +const velocity = meters.over(seconds); +expectTrue(value(velocity).hasType>()); + +expectTrue(value(meters.plus(meters)).hasType>()); +expectTrue(value(meters.minus(meters)).hasType>()); +expectTrue(value(meters.negate()).hasType>()); +expectTrue(value(meters.scale(2)).hasType>()); + +expectTrue(value(velocity.squared()).hasType>()); +expectTrue(value(absement.cubed()).hasType>()); +expectTrue(value(absement.inverse()).hasType>()); + +expectTrue(value(volume.times(volume)).hasType>()); +expectTrue(value(volume.cubed().inverse()).hasType>()); + +// Error usages + +declare const volume: Volume; +declare const area: Area; +declare const length: Length; + +expectTrue(value(Measure.dimension("x" as "x" | "y")).hasType()); +expectTrue(value(Measure.dimension("x" as string)).hasType()); + +// @ts-expect-error Area and volume have different units and cannot be added +volume.plus(area); +// @ts-expect-error Area and volume have different units and cannot be subtracted +area.minus(volume); + +// TODO(jscheinerman): This should be an error but is not because of structural typing +length.plus(velocity); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..6fb3926 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "lib": ["es5"], + "baseUrl": ".", + "paths": { "safe-units": ["../src"] } + } +} \ No newline at end of file diff --git a/test/types/exponents.ts b/test/types/exponents.ts deleted file mode 100644 index a4dcb34..0000000 --- a/test/types/exponents.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AddendOf, MultiplicandOf, Exponent, ProductOf, SubtrahendOf } from "../../src/exponent"; -import { IsSame } from "./utils"; - -type AddendOf3 = IsSame<"-5" | "-4" | "-3" | "-2" | "-1" | "0" | "1" | "2", AddendOf<"3">>; // $ExpectType true -type AddendOf0 = AddendOf<"0">; // $ExpectType Exponent - -type SubtrahendOf3 = IsSame<"-2" | "-1" | "0" | "1" | "2" | "3" | "4" | "5", SubtrahendOf<"3">>; // $ExpectType true -type SubtrahendOf0 = SubtrahendOf<"0">; // $ExpectType Exponent - -type MultiplicandOf2 = IsSame<"-2" | "-1" | "0" | "1" | "2", MultiplicandOf<"2">>; // $ExpectType true -type MultiplicandOf3 = IsSame<"-1" | "0" | "1", MultiplicandOf<"3">>; // $ExpectType true -type MultiplicandOf0 = MultiplicandOf<"0">; // $ExpectType Exponent - -type ProductOf2 = IsSame<"-4" | "-2" | "0" | "2" | "4", ProductOf<"2">>; // $ExpectType true diff --git a/test/types/index.d.ts b/test/types/index.d.ts deleted file mode 100644 index 38e99dd..0000000 --- a/test/types/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -// TypeScript Version: 2.9 diff --git a/test/types/measures.ts b/test/types/measures.ts deleted file mode 100644 index 5913652..0000000 --- a/test/types/measures.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Acceleration, Area, kilograms, Measure, meters, newtons, seconds, Volume } from "safe-units"; - -// Valid usages - -Measure.dimension("x"); // $ExpectType GenericMeasure - -const m = meters; // $ExpectType GenericMeasure -const s = seconds; // $ExpectType GenericMeasure -const n = newtons; // $ExpectType GenericMeasure - -const a = newtons.over(kilograms); // $ExpectType GenericMeasure -const accel: Acceleration = a; // $ExpectType GenericMeasure - -const absement = meters.times(seconds); // $ExpectType GenericMeasure -const velocity = meters.over(seconds); // $ExpectType GenericMeasure - -meters.plus(meters); // $ExpectType GenericMeasure -meters.minus(meters); // $ExpectType GenericMeasure -meters.negate(); // $ExpectType GenericMeasure -meters.scale(2); // $ExpectType GenericMeasure - -velocity.squared(); // $ExpectType GenericMeasure -absement.cubed(); // $ExpectType GenericMeasure -absement.inverse(); // $ExpectType GenericMeasure -velocity.toThe("0"); // $ExpectType GenericMeasure - -Measure.sqrt(velocity.toThe("-4")); // $ExpectType GenericMeasure -Measure.cbrt(absement.toThe("3")); // $ExpectType GenericMeasure - -// Error usages - -declare const volume: Volume; -declare const area: Area; - -Measure.dimension("x" as "x" | "y"); // $ExpectType never -Measure.dimension("x" as string); // $ExpectType never - -volume.times(volume); // $ExpectError -const volumeInverse = volume.inverse(); -volume.over(volumeInverse); // $ExpectError - -volume.plus(area); // $ExpectError -area.minus(volume); // $ExpectError - -const sq = volume.squared; // $ExpectType never -const cu = area.cubed; // $ExpectType never -area.toThe("4"); // $ExpectError -volume.toThe("-2"); // $ExpectError - -Measure.sqrt(volume); // $ExpectError -Measure.cbrt(area); // $ExpectError diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json deleted file mode 100644 index ddd0445..0000000 --- a/test/types/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "lib": ["es5"], - "baseUrl": ".", - "paths": { "safe-units": ["../../src"] } - } -} \ No newline at end of file diff --git a/test/types/tslint.json b/test/types/tslint.json deleted file mode 100644 index 42701ce..0000000 --- a/test/types/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "dtslint/dtslint.json", - "rules": { - "no-useless-files": false - } -} \ No newline at end of file diff --git a/test/types/units.ts b/test/types/units.ts deleted file mode 100644 index a8b4088..0000000 --- a/test/types/units.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Exponent } from "../../src/exponent"; -import { - MultiplyUnits, - RadicandUnit, - NthRootUnit, - MultiplicandUnit, - DivideUnits, - ExponentiateUnit, - AllowedExponents, -} from "../../src/measure/unitTypeArithmetic"; -import { IsSame } from "./utils"; -import { IsSingleStringLiteral } from "../../src/measure/typeUtils"; - -type Extends = A extends B ? true : false; - -// MultiplyUnits - -type SelfMultiplication = MultiplyUnits<{ a: "2" }, { a: "2" }>; // $ExpectType { a: "4"; } -type MultiplySeveralDimensions = MultiplyUnits<{ a: "2"; b: "-1" }, { a: "-2"; b: "2"; c: "1" }>; // $ExpectType { b: "1"; c: "1"; } - -// MultiplicandUnit - -type MultiplicandAllowsOtherDimsWithAnyExponent = Extends<{ b: Exponent }, MultiplicandUnit<{ a: "2" }>>; // $ExpectType true -type MultiplicandRejectsSameDimWithBadExponent = Extends<{ a: "-4" }, MultiplicandUnit<{ a: "-2" }>>; // $ExpectType false -type MultiplicandAcceptsSameDimWithGoodExponent = Extends<{ a: "2" }, MultiplicandUnit<{ a: "3" }>>; // $ExpectType true -type MultiplicandAllowsMultiDims = Extends<{ a: "3"; c: "-4"; d: "5" }, MultiplicandUnit<{ a: "1"; c: "2" }>>; // $ExpectType true -type MultiplicandRejectsMultiDims = Extends<{ a: "1"; c: "0" }, MultiplicandUnit<{ a: "5"; b: "-5" }>>; // $ExpectType false - -// DivideUnits - -type Division = DivideUnits<{ a: "2"; b: "-1" }, { a: "2"; b: "-2"; c: "-1" }>; // $ExpectType { b: "1"; c: "1"; } - -// ExponentiateUnit - -type RaisingToTheZero = ExponentiateUnit<{ a: "2"; b: "-1" }, "0">; // $ExpectType {} -type RaisingToTheOne = ExponentiateUnit<{ a: "2"; b: "3" }, "1">; // $ExpectType { a: "2"; b: "3"; } -type Squaring = ExponentiateUnit<{ a: "2"; b: "-1" }, "2">; // $ExpectType { a: "4"; b: "-2"; } -type Cubing = ExponentiateUnit<{ a: "1" }, "3">; // $ExpectType { a: "3"; } - -// AllowedExponents - -type AllowedLargeExponents = AllowedExponents<{ a: "1"; b: "0" }>; // $ExpectType Exponent -type AllowedMediumExponents = IsSame<"-2" | "-1" | "0" | "1" | "2", AllowedExponents<{ a: "2"; b: "1" }>>; // $ExpectType true -type AllowedSmallExponents = IsSame<"-1" | "0" | "1", AllowedExponents<{ a: "3"; b: "1" }>>; // $ExpectType true - -// NthRootUnit - -type SquareRooting = NthRootUnit<{ a: "4"; b: "-2" }, "2">; // $ExpectType { a: "2"; b: "-1"; } -type CubeRooting = NthRootUnit<{ a: "3"; b: "-3" }, "3">; // $ExpectType { a: "1"; b: "-1"; } -type NthRootRejectsZero = NthRootUnit<{ a: "0" }, "0">; // $ExpectError -type NthRootRejectsNegative = NthRootUnit<{ a: "1" }, "-1">; // $ExpectError - -// RadicandUnit - -type RadicandAcceptsPerfectSquares = Extends<{ a: "2"; b: "-4"; c: "0" }, RadicandUnit<"2">>; // $ExpectType true -type RadicandRejectsNonPerfectSquares = Extends<{ a: "2"; b: "1" }, RadicandUnit<"2">>; // $ExpectType false -type RadicandAcceptsPerfectCubes = Extends<{ a: "3"; b: "-3" }, RadicandUnit<"3">>; // $ExpectType true -type RadicandRejectsNonPerfectCubes = Extends<{ a: "3"; b: "2" }, RadicandUnit<"3">>; // $ExpectType false -type RadicandRejectsZero = RadicandUnit<"0">; // $ExpectError - -// IsSingleStringLiteral - -type SingleLiteralWorks = IsSingleStringLiteral<"A">; // $ExpectType true -type UnionLiteralWorks = IsSingleStringLiteral<"A" | "B">; // $ExpectType false -type StringTypeWorks = IsSingleStringLiteral; // $ExpectType false diff --git a/test/types/utils.ts b/test/types/utils.ts deleted file mode 100644 index 88d867d..0000000 --- a/test/types/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export type IsSame = [A] extends [B] ? ([B] extends [A] ? true : false) : false; diff --git a/test/units.ts b/test/units.ts new file mode 100644 index 0000000..97a9dae --- /dev/null +++ b/test/units.ts @@ -0,0 +1,17 @@ +import { MultiplyUnits, DivideUnits } from "../src/measure/unitTypeArithmetic"; +import { Equivalent, Expect, ExpectFalse } from "./utils"; +import { IsSingleStringLiteral } from "../src/measure/typeUtils"; + +export type TestCases = [ + // MultiplyUnits + Expect, { a: 4 }>>, + Expect, { b: -3; c: 1 }>>, + + // DivideUnits + Expect, { b: -4; c: 1 }>>, + + // IsSingleStringLiteral + Expect>, + ExpectFalse>, + ExpectFalse>, +]; diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..e14b2d1 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,10 @@ +export type Expect = T; +export type ExpectFalse = T; +export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; +export type Equivalent = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false; + +export function value(_value: T): { hasType: () => Equal } { + return { hasType: <_U>() => true as any }; +} + +export function expectTrue(_value: true) {} diff --git a/yarn.lock b/yarn.lock index ed3f996..e65ce84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -837,11 +837,6 @@ dependencies: undici-types "~5.26.4" -"@types/parsimmon@^1.3.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.0.tgz#ffb81cb023ff435a41d4710a29ab23c561dc9fdf" - integrity sha512-bsTIJFVQv7jnvNiC42ld2pQW2KRI+pAG243L+iATvqzy3X6+NH1obz2itRKDZZ8VVhN3wjwYax/VBGCcXzgTqQ== - "@types/prop-types@*": version "15.5.8" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce" @@ -1017,10 +1012,6 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -1031,10 +1022,6 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1058,11 +1045,6 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -any-promise@^1.0.0, any-promise@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1139,14 +1121,6 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1259,7 +1233,7 @@ buffer-from@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" -builtin-modules@^1.0.0, builtin-modules@^1.1.1: +builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1294,17 +1268,7 @@ caniuse-lite@^1.0.30001587: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz#fad74fa006aef0f01e8e5c0a5540c74d8d36ec6f" integrity sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg== -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.3.0: +chalk@^2.0.0: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1391,10 +1355,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - commonmark@^0.31.0: version "0.31.0" resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.31.0.tgz#4ac57c61f0d7f5ef82d79447a972c61226ef5abc" @@ -1567,13 +1527,6 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -definitelytyped-header-parser@Microsoft/definitelytyped-header-parser#production: - version "0.0.0" - resolved "https://codeload.github.com/Microsoft/definitelytyped-header-parser/tar.gz/6ff181ecd781706f200f8a780b0133a85fd0a69f" - dependencies: - "@types/parsimmon" "^1.3.0" - parsimmon "^1.2.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1589,10 +1542,6 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -diff@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -1612,17 +1561,6 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" -dtslint@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dtslint/-/dtslint-0.3.0.tgz#918e664a6f7e1f54ba22088daa2bc6e64a6001c1" - integrity sha512-3oWL8MD+2nKaxmNzrt8EAissP63hNSJ4OLr/itvNnPdAAl+7vxnjQ8p2Zdk0MNgdenqwk7GcaUDz7fQHaPgCyA== - dependencies: - definitelytyped-header-parser Microsoft/definitelytyped-header-parser#production - fs-promise "^2.0.0" - strip-json-comments "^2.0.1" - tslint "^5.9.1" - typescript next - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1776,7 +1714,7 @@ escalade@^3.1.1, escalade@^3.1.2: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2059,24 +1997,6 @@ free-style@3.1.0: resolved "https://registry.yarnpkg.com/free-style/-/free-style-3.1.0.tgz#4e2996029534e6b1731611d843437b9e2f473f08" integrity sha512-vJujYSIyT30iDoaoeigNAxX4yB1RUrh+N2ZMhIElMr3BvCuGXOw7XNJMEEJkDUeamK2Rnb/IKFGKRKlTWIGRWA== -fs-extra@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" - integrity sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - -fs-promise@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-2.0.3.tgz#f64e4f854bcf689aa8bddcba268916db3db46854" - integrity sha1-9k5PhUvPaJqovdy6JokW2z20aFQ= - dependencies: - any-promise "^1.3.0" - fs-extra "^2.0.0" - mz "^2.6.0" - thenify-all "^1.6.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2175,17 +2095,6 @@ glob@^10.3.7: minipass "^7.0.4" path-scurry "^1.11.0" -glob@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -2247,7 +2156,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2261,12 +2170,6 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -3028,11 +2931,7 @@ jest@^29.7.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - -js-yaml@^3.13.1, js-yaml@^3.7.0: +js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -3113,13 +3012,6 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3308,15 +3200,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mz@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3380,7 +3263,7 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8" integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ== -object-assign@^4.0.1, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3497,11 +3380,6 @@ parse5@^7.0.0, parse5@^7.1.1: dependencies: entities "^4.4.0" -parsimmon@^1.2.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/parsimmon/-/parsimmon-1.12.0.tgz#886a442fb30b5fc3c8e7c4994050f5cdcfe0ea90" - integrity sha512-uC/BjuSfb4jfaWajKCp1mVncXXq+V1twbcYChbTxN3GM7fn+8XoHwUdvUz+PTaFtDSCRQxU8+Rnh+iMhAkVwdw== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3520,10 +3398,6 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -3733,12 +3607,6 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.3.2: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - dependencies: - path-parse "^1.0.5" - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3797,7 +3665,7 @@ scheduler@^0.12.0: loose-envify "^1.1.0" object-assign "^4.1.1" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -4026,12 +3894,6 @@ string.prototype.trimstart@^1.0.8: dependencies: ansi-regex "^5.0.1" -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -4060,19 +3922,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - supports-color@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" @@ -4125,20 +3979,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -thenify-all@^1.0.0, thenify-all@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" - integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= - dependencies: - any-promise "^1.0.0" - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -4211,40 +4051,11 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^1.8.0, tslib@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" - tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tslint@^5.9.1: - version "5.11.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" - integrity sha1-mPMMAurjzecAYgHkwzywi0hYHu0= - dependencies: - babel-code-frame "^6.22.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^3.2.0" - glob "^7.1.1" - js-yaml "^3.7.0" - minimatch "^3.0.4" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.27.2" - -tsutils@^2.27.2: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -4320,11 +4131,6 @@ typescript@^5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== -typescript@next: - version "3.2.0-dev.20181116" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.0-dev.20181116.tgz#0a405b3385d6f180df78bf557167c0a608b5a501" - integrity sha512-+UUHAGhfccJe1ZnHbqp0uixDjT/yMguznOvF1p+16ytQPJaFo2MfIt6on4PZlgl7VhdxtENSfzl44xxjWsfj8Q== - typestyle@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/typestyle/-/typestyle-2.4.0.tgz#df5bae6ff15093f5ce51f0caac5ef79428f64e78"