Skip to content

Commit

Permalink
typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
Duane Millar Barlow committed May 31, 2022
1 parent 449af11 commit 109c85c
Show file tree
Hide file tree
Showing 17 changed files with 962 additions and 78 deletions.
11 changes: 5 additions & 6 deletions .eslintrc.json
@@ -1,9 +1,7 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"plugins": ["@typescript-eslint"],
"parser": "@typescript-eslint/parser",
"env": {
"es6": true,
"node": true,
Expand All @@ -15,6 +13,7 @@
"no-sparse-arrays": 0,
"no-unexpected-multiline": 0,
"comma-dangle": ["error", "never"],
"semi": [2, "always"]
"semi": [2, "always"],
"@typescript-eslint/no-empty-function": 0
}
}
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -3,3 +3,5 @@ dist/
node_modules/
test/output/*-changed.svg
test/output/*-changed.html
tsconfig.tsbuildinfo
yarn-error.log
14 changes: 14 additions & 0 deletions .mocharc.json
@@ -0,0 +1,14 @@
{
"require": ["module-alias/register.js", "ts-node/register"],
"loader": "ts-node/esm",
"extensions": ["js", "ts"],
"spec": [
"test/**/*-test.*",
"test/plot.js"
],
"watch-files": [
"bundle.js",
"test",
"src"
]
}
28 changes: 22 additions & 6 deletions package.json
Expand Up @@ -8,13 +8,14 @@
},
"license": "ISC",
"type": "module",
"main": "src/index.js",
"module": "src/index.js",
"main": "dist/index.js",
"module": "dist/index.js",
"jsdelivr": "dist/plot.umd.min.js",
"unpkg": "dist/plot.umd.min.js",
"exports": {
"mocha": "./src/index.js",
"umd": "./dist/plot.umd.min.js",
"default": "./src/index.js"
"default": "./dist/index.js"
},
"repository": {
"type": "git",
Expand All @@ -25,8 +26,11 @@
"src/**/*.js"
],
"scripts": {
"test": "mkdir -p test/output && mocha -r module-alias/register 'test/**/*-test.js' test/plot.js && eslint src test",
"prepublishOnly": "rm -rf dist && rollup -c",
"test": "yarn test:typecheck & yarn test:lint & yarn test:mocha",
"test:mocha": "mkdir -p test/output && mocha --conditions=mocha --files",
"test:lint": "eslint src test",
"test:typecheck": "yarn tsc --noEmit",
"prepublishOnly": "rm -rf dist && rollup -c && tsc",
"postpublish": "git push && git push --tags",
"dev": "vite"
},
Expand All @@ -38,15 +42,27 @@
"@rollup/plugin-commonjs": "22",
"@rollup/plugin-json": "4",
"@rollup/plugin-node-resolve": "13",
"@rollup/plugin-typescript": "^8.3.2",
"@types/d3": "^7.4.0",
"@types/expect": "^24.3.0",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.35",
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"canvas": "2",
"eslint": "8",
"eslint": "^8.16.0",
"glob": "^8.0.3",
"htl": "0.3",
"js-beautify": "1",
"jsdom": "19",
"mocha": "10",
"module-alias": "2",
"rollup": "2",
"rollup-plugin-terser": "7",
"ts-node": "^10.8.0",
"tslib": "^2.4.0",
"typescript": "^4.6.4",
"typescript-module-alias": "^1.0.2",
"vite": "2"
},
"dependencies": {
Expand Down
2 changes: 2 additions & 0 deletions rollup.config.js
Expand Up @@ -4,6 +4,7 @@ import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import node from "@rollup/plugin-node-resolve";
import * as meta from "./package.json";
import typescript from "@rollup/plugin-typescript";

const filename = meta.name.split("/").pop();

Expand All @@ -26,6 +27,7 @@ const config = {
banner: `// ${meta.name} v${meta.version} Copyright ${copyrights.join(", ")}`
},
plugins: [
typescript(),
commonjs(),
json(),
node()
Expand Down
48 changes: 37 additions & 11 deletions src/curve.ts
Expand Up @@ -20,8 +20,32 @@ import {
curveStepAfter,
curveStepBefore
} from "d3";
import type { CurveFactory, CurveBundleFactory, CurveCardinalFactory, CurveCatmullRomFactory } from "d3";

const curves = new Map([
type CurveFunction = CurveFactory | CurveBundleFactory | CurveCardinalFactory | CurveCatmullRomFactory;
type CurveName =
| "basis"
| "basis-closed"
| "basis-open"
| "bundle"
| "bump-x"
| "bump-y"
| "cardinal"
| "cardinal-closed"
| "cardinal-open"
| "catmull-rom"
| "catmull-rom-closed"
| "catmull-rom-open"
| "linear"
| "linear-closed"
| "monotone-x"
| "monotone-y"
| "natural"
| "step"
| "step-after"
| "step-before";

const curves = new Map<CurveName, CurveFunction>([
["basis", curveBasis],
["basis-closed", curveBasisClosed],
["basis-open", curveBasisOpen],
Expand All @@ -44,19 +68,21 @@ const curves = new Map([
["step-before", curveStepBefore]
]);

export function Curve(curve = curveLinear, tension) {
export function Curve(
curve: CurveName | CurveFunction = curveLinear,
tension?: number
): CurveFunction {
if (typeof curve === "function") return curve; // custom curve
const c = curves.get(`${curve}`.toLowerCase());
const c = curves.get(`${curve}`.toLowerCase() as CurveName);
if (!c) throw new Error(`unknown curve: ${curve}`);

if (tension !== undefined) {
switch (c) {
case curveBundle: return c.beta(tension);
case curveCardinalClosed:
case curveCardinalOpen:
case curveCardinal: return c.tension(tension);
case curveCatmullRomClosed:
case curveCatmullRomOpen:
case curveCatmullRom: return c.alpha(tension);
if ("beta" in c) {
return c.beta(tension);
} else if ("tension" in c) {
return c.tension(tension);
} else if ("alpha" in c) {
return c.alpha(tension);
}
}
return c;
Expand Down
20 changes: 10 additions & 10 deletions src/defined.ts
@@ -1,29 +1,29 @@
import {ascending, descending} from "d3";
import {ascending, descending, type Primitive} from "d3";

export function defined(x) {
export function defined(x: Primitive | undefined): boolean {
return x != null && !Number.isNaN(x);
}

export function ascendingDefined(a, b) {
return defined(b) - defined(a) || ascending(a, b);
export function ascendingDefined(a: Primitive | undefined, b: Primitive | undefined): number {
return +defined(b) - +defined(a) || ascending(a, b);
}

export function descendingDefined(a, b) {
return defined(b) - defined(a) || descending(a, b);
export function descendingDefined(a: Primitive | undefined, b: Primitive | undefined): number {
return +defined(b) - +defined(a) || descending(a, b);
}

export function nonempty(x) {
export function nonempty(x: unknown): boolean {
return x != null && `${x}` !== "";
}

export function finite(x) {
export function finite(x: number): number {
return isFinite(x) ? x : NaN;
}

export function positive(x) {
export function positive(x: number): number {
return x > 0 && isFinite(x) ? x : NaN;
}

export function negative(x) {
export function negative(x: number): number {
return x < 0 && isFinite(x) ? x : NaN;
}
26 changes: 14 additions & 12 deletions src/format.ts
@@ -1,33 +1,35 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {format as isoFormat} from "isoformat";
import {string} from "./options.js";
import {memoize1} from "./memoize.js";

const numberFormat = memoize1(locale => new Intl.NumberFormat(locale));
const monthFormat = memoize1((locale, month) => new Intl.DateTimeFormat(locale, {timeZone: "UTC", month}));
const weekdayFormat = memoize1((locale, weekday) => new Intl.DateTimeFormat(locale, {timeZone: "UTC", weekday}));

export function formatNumber(locale = "en-US") {
const numberFormat = memoize1<Intl.NumberFormat>((locale: string | string[] | undefined) => new Intl.NumberFormat(locale));
const monthFormat = memoize1<Intl.DateTimeFormat>((locale: string | string[] | undefined, month: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined) => new Intl.DateTimeFormat(locale, {timeZone: "UTC", month}));
const weekdayFormat = memoize1<Intl.DateTimeFormat>((locale: string | string[] | undefined, weekday: "long" | "short" | "narrow" | undefined) => new Intl.DateTimeFormat(locale, {timeZone: "UTC", weekday}));

export function formatNumber(locale = "en-US"): (value: any) => string | undefined {
const format = numberFormat(locale);
return i => i != null && !isNaN(i) ? format.format(i) : undefined;
return (i: any) => i != null && !isNaN(i) ? format.format(i) : undefined;
}

export function formatMonth(locale = "en-US", month = "short") {
export function formatMonth(locale = "en-US", month: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined = "short") {
const format = monthFormat(locale, month);
return i => i != null && !isNaN(i = new Date(Date.UTC(2000, +i))) ? format.format(i) : undefined;
return (i: Date | number | null | undefined) => i != null && !isNaN(i = +new Date(Date.UTC(2000, +i))) ? format.format(i) : undefined;
}

export function formatWeekday(locale = "en-US", weekday = "short") {
export function formatWeekday(locale = "en-US", weekday: "long" | "short" | "narrow" | undefined = "short") {
const format = weekdayFormat(locale, weekday);
return i => i != null && !isNaN(i = new Date(Date.UTC(2001, 0, +i))) ? format.format(i) : undefined;
return (i: Date | number | null | undefined) => i != null && !isNaN(i = +new Date(Date.UTC(2001, 0, +i))) ? format.format(i) : undefined;
}

export function formatIsoDate(date) {
export function formatIsoDate(date: Date): string {
return isoFormat(date, "Invalid Date");
}

export function formatAuto(locale = "en-US") {
export function formatAuto(locale = "en-US"): (value: any) => string | number | undefined {
const number = formatNumber(locale);
return v => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v);
return (v: any) => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v);
}

// TODO When Plot supports a top-level locale option, this should be removed
Expand Down
2 changes: 1 addition & 1 deletion src/math.ts
@@ -1 +1 @@
export const radians = Math.PI / 180;
export const radians: number = Math.PI / 180;
9 changes: 5 additions & 4 deletions src/memoize.ts
@@ -1,7 +1,8 @@
export function memoize1(compute) {
let cacheValue, cacheKeys = {};
return (...keys) => {
if (cacheKeys.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) {
/* eslint-disable @typescript-eslint/no-explicit-any */
export function memoize1<T>(compute: (...rest: any[]) => T) {
let cacheValue: T, cacheKeys: any[] | undefined;
return (...keys: any[]) => {
if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) {
cacheKeys = keys;
cacheValue = compute(...keys);
}
Expand Down
8 changes: 4 additions & 4 deletions src/transforms/bin.js
Expand Up @@ -88,10 +88,10 @@ function binn(
stroke,
x1, x2, // consumed if x is an output
y1, y2, // consumed if y is an output
domain, // eslint-disable-line no-unused-vars
cumulative, // eslint-disable-line no-unused-vars
thresholds, // eslint-disable-line no-unused-vars
interval, // eslint-disable-line no-unused-vars
domain, // eslint-disable-line @typescript-eslint/no-unused-vars
cumulative, // eslint-disable-line @typescript-eslint/no-unused-vars
thresholds, // eslint-disable-line @typescript-eslint/no-unused-vars
interval, // eslint-disable-line @typescript-eslint/no-unused-vars
...options
} = inputs;
const [GZ, setGZ] = maybeColumn(z);
Expand Down
6 changes: 6 additions & 0 deletions src/types/isoformat.d.ts
@@ -0,0 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'isoformat' {
export function format(value: any, fallback: string): string;
export function format(value: any, fallback: any): any;
export function parse(value: string): Date;
}
2 changes: 1 addition & 1 deletion src/warnings.ts
Expand Up @@ -6,7 +6,7 @@ export function consumeWarnings() {
return w;
}

export function warn(message) {
export function warn(message: string) {
console.warn(message);
++warnings;
}
2 changes: 1 addition & 1 deletion test/legends/legends-test.ts
@@ -1,5 +1,5 @@
import * as Plot from "@observablehq/plot";
import assert from "assert";
import * as assert from "assert";

it("Plot.legend({color: {type:'identity'}}) returns undefined", () => {
const l = Plot.legend({color: {type: "identity"}});
Expand Down
21 changes: 21 additions & 0 deletions tsconfig.json
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": [
"esnext",
"dom"
],
"strict": true,
"stripInternal": true,
"outDir": "dist",
"allowJs": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"paths": {
"@observablehq/plot": ["./src/index.js"]
}
},
"ts-node": { "files": true },
"include": ["src/**/*"]
}
25 changes: 22 additions & 3 deletions vite.config.js
@@ -1,13 +1,32 @@
import path from "path";
import {defineConfig} from "vite";
import glob from "glob";

// Vite will automatically try to resolve to .ts files when an imported .js file
// doesn't exist, but only if the importer is .ts. In order to fall back on .ts
// for all.js imports that don't exist, we provide a customResolver below that
// will do this based on the js and ts files we have in src/.
// Once all of the files in src/ are converted to TypeScript we can remove
// this customLoader.
const typescriptPaths = new Set(glob.sync(`${path.resolve("./src")}/**/*.ts`));
const javascriptPaths = new Set(glob.sync(`${path.resolve("./src")}/**/*.js`));

export default defineConfig({
root: "./test/plots",
publicDir: path.resolve("./test"),
resolve: {
alias: {
"@observablehq/plot": path.resolve("./src/index.js")
}
alias: [
{ find: "@observablehq/plot", replacement: path.resolve("./src/index.js") },
{
find: RegExp(`^(.*).js$`), replacement: "$1", customResolver: (importee, importer) => {
const base = path.join(path.dirname(importer), importee);
const js = `${base}.js`;
const ts = `${base}.ts`;
if (javascriptPaths.has(js)) return js;
if (typescriptPaths.has(ts)) return ts;
}
}
]
},
server: {
port: 8008,
Expand Down

0 comments on commit 109c85c

Please sign in to comment.