From 1815d0a78e4e4641b17c90bc555e4412e6b35779 Mon Sep 17 00:00:00 2001 From: Chapman Pendery <35637443+cpendery@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:04:04 -0400 Subject: [PATCH] fix: complex paths (#291) * feat: update parser to handle backtick quoted strings, strings without ending quotes, and strings with quotes with content immediately following Signed-off-by: Chapman Pendery * fix: unwrap strings + escapes during parsing & support wide chars Signed-off-by: Chapman Pendery * fix: path completions Signed-off-by: Chapman Pendery --------- Signed-off-by: Chapman Pendery --- package-lock.json | 777 +++++++++++++++++- package.json | 6 +- src/runtime/alias.ts | 4 +- src/runtime/parser.ts | 82 +- src/runtime/runtime.ts | 42 +- src/runtime/suggestion.ts | 21 +- src/runtime/utils.ts | 59 +- .../runtime/__snapshots__/alias.test.ts.snap | 16 + .../runtime/__snapshots__/parser.test.ts.snap | 163 +++- src/tests/runtime/alias.test.ts | 17 +- src/tests/runtime/parser.test.ts | 9 +- src/utils/shell.ts | 6 +- 12 files changed, 1123 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4cdbe66..cbc4fba4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "lint-staged": "^15.2.2", "prettier": "3.0.3", "ts-jest": "^29.1.1", - "ts-node": "^10.9.2", + "tsx": "^4.19.1", "typescript": "^5.2.2" }, "engines": { @@ -740,6 +740,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -752,11 +754,397 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1909,25 +2297,33 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node18": { "version": "18.2.2", @@ -2363,6 +2759,8 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -2435,7 +2833,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/argparse": { "version": "1.0.10", @@ -3233,7 +3633,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -3407,6 +3809,8 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -3616,6 +4020,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4463,6 +4906,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -7652,6 +8107,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -8404,6 +8868,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -8442,6 +8908,25 @@ } } }, + "node_modules/tsx": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8625,7 +9110,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/v8-to-istanbul": { "version": "9.1.3", @@ -8973,6 +9460,8 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -9512,6 +10001,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -9521,6 +10012,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -9528,6 +10021,174 @@ } } }, + "@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "dev": true, + "optional": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -10334,25 +10995,33 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node18": { "version": "18.2.2", @@ -10684,7 +11353,9 @@ "version": "8.3.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "ajv": { "version": "8.12.0", @@ -10729,7 +11400,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "argparse": { "version": "1.0.10", @@ -11291,7 +11964,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "cross-spawn": { "version": "7.0.3", @@ -11412,7 +12087,9 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "diff-sequences": { "version": "29.6.3", @@ -11583,6 +12260,38 @@ "is-symbol": "^1.0.2" } }, + "esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -12193,6 +12902,15 @@ "get-intrinsic": "^1.1.1" } }, + "get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -14432,6 +15150,12 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -14936,6 +15660,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -14952,6 +15678,17 @@ "yn": "3.1.1" } }, + "tsx": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", + "dev": true, + "requires": { + "esbuild": "~0.23.0", + "fsevents": "~2.3.3", + "get-tsconfig": "^4.7.5" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -15072,7 +15809,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "v8-to-istanbul": { "version": "9.1.3", @@ -15335,7 +16074,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 648bd848..162cd2ee 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ ], "scripts": { "build": "tsc", - "dev": "node --loader ts-node/esm src/index.ts -V", + "dev": "node --import=tsx src/index.ts -V", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "test:e2e": "tui-test", "lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check", "lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write", - "debug": "node --inspect --loader ts-node/esm src/index.ts -V", + "debug": "node --inspect --import=tsx src/index.ts -V", "pre-commit": "lint-staged" }, "repository": { @@ -77,7 +77,7 @@ "lint-staged": "^15.2.2", "prettier": "3.0.3", "ts-jest": "^29.1.1", - "ts-node": "^10.9.2", + "tsx": "^4.19.1", "typescript": "^5.2.2" }, "lint-staged": { diff --git a/src/runtime/alias.ts b/src/runtime/alias.ts index fe6f4ea8..7359ff69 100644 --- a/src/runtime/alias.ts +++ b/src/runtime/alias.ts @@ -24,7 +24,7 @@ const loadBashAliases = async () => { .split("\n") .forEach((line) => { const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("="); - loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " "); + loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Bash); }); }; @@ -40,7 +40,7 @@ const loadZshAliases = async () => { .split("\n") .forEach((line) => { const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("="); - loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " "); + loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Zsh); }); }; diff --git a/src/runtime/parser.ts b/src/runtime/parser.ts index 652d9e0f..2e9a5081 100644 --- a/src/runtime/parser.ts +++ b/src/runtime/parser.ts @@ -1,33 +1,68 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import wcwidth from "wcwidth"; +import { Shell } from "../utils/shell.js"; +import { getShellWhitespaceEscapeChar } from "./utils.js"; + export type CommandToken = { token: string; + tokenLength: number; complete: boolean; isOption: boolean; isPersistent?: boolean; isPath?: boolean; isPathComplete?: boolean; - isQuoted?: boolean; + isQuoted?: boolean; // used for any token starting with quotes + isQuoteContinued?: boolean; // used for strings that are fully wrapped in quotes with content after the quotes }; const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/; const spaceRegex = /\s/; -export const parseCommand = (command: string): CommandToken[] => { +export const parseCommand = (command: string, shell: Shell): CommandToken[] => { const lastCommand = command.split(cmdDelim).at(-1)?.trimStart(); - return lastCommand ? lex(lastCommand) : []; + const tokens = lastCommand ? lex(lastCommand, shell) : []; + return sanitizeTokens(tokens, shell); +}; + +const sanitizeTokens = (cmdTokens: CommandToken[], shell: Shell): CommandToken[] => unwrapQuotedTokens(unescapeSpaceTokens(cmdTokens, shell)); + +// remove escapes around spaces +const unescapeSpaceTokens = (cmdTokens: CommandToken[], shell: Shell): CommandToken[] => { + const escapeChar = getShellWhitespaceEscapeChar(shell); + return cmdTokens.map((cmdToken) => { + const { token, isQuoted } = cmdToken; + if (!isQuoted && token.includes(`${escapeChar} `)) { + return { ...cmdToken, token: token.replaceAll(`${escapeChar} `, " ") }; + } + return cmdToken; + }); }; -const lex = (command: string): CommandToken[] => { +// need to unwrap tokens that are quoted with content after the quotes like `"hello"world` +const unwrapQuotedTokens = (cmdTokens: CommandToken[]): CommandToken[] => { + return cmdTokens.map((cmdToken) => { + const { token, isQuoteContinued } = cmdToken; + if (isQuoteContinued) { + const quoteChar = token[0]; + const unquotedToken = token.replaceAll(`\\${quoteChar}`, "\u001B").replaceAll(quoteChar, "").replaceAll("\u001B", quoteChar); + return { ...cmdToken, token: unquotedToken }; + } + return cmdToken; + }); +}; + +const lex = (command: string, shell: Shell): CommandToken[] => { const tokens: CommandToken[] = []; - let [readingQuotedString, readingFlag, readingCmd] = [false, false, false]; + const escapeChar = getShellWhitespaceEscapeChar(shell); + let [readingQuotedString, readingQuoteContinuedString, readingFlag, readingCmd] = [false, false, false, false]; let readingIdx = 0; let readingQuoteChar = ""; [...command].forEach((char, idx) => { - const reading = readingQuotedString || readingFlag || readingCmd; - if (!reading && (char === `'` || char === `"`)) { + const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd; + if (!reading && (char === `'` || char === `"` || char == "`")) { [readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char]; return; } else if (!reading && char === `-`) { @@ -38,44 +73,71 @@ const lex = (command: string): CommandToken[] => { return; } - if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "\\") { + if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar && !spaceRegex.test(command.at(idx + 1) ?? " ")) { + readingQuotedString = false; + readingQuoteContinuedString = true; + } else if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar) { readingQuotedString = false; const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]); tokens.push({ token: command.slice(readingIdx + 1, idx), + tokenLength: wcwidth(command.slice(readingIdx + 1, idx)) + 2, // +2 for both quotes complete, isOption: false, isQuoted: true, }); + } else if (readingQuoteContinuedString && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) { + readingQuoteContinuedString = false; + tokens.push({ + token: command.slice(readingIdx, idx), + tokenLength: wcwidth(command.slice(readingIdx, idx)), + complete: true, + isOption: false, + isQuoted: true, + isQuoteContinued: true, + }); } else if ((readingFlag && spaceRegex.test(char)) || char === "=") { readingFlag = false; tokens.push({ token: command.slice(readingIdx, idx), + tokenLength: wcwidth(command.slice(readingIdx, idx)), complete: true, isOption: true, }); - } else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== "\\") { + } else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) { readingCmd = false; tokens.push({ token: command.slice(readingIdx, idx), + tokenLength: wcwidth(command.slice(readingIdx, idx)), complete: true, isOption: false, }); } }); - const reading = readingQuotedString || readingFlag || readingCmd; + const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd; if (reading) { if (readingQuotedString) { tokens.push({ token: command.slice(readingIdx + 1), + tokenLength: wcwidth(command.slice(readingIdx + 1)) + 1, // +1 for the leading quote + complete: false, + isOption: false, + isQuoted: true, + }); + } else if (readingQuoteContinuedString) { + tokens.push({ + token: command.slice(readingIdx), + tokenLength: wcwidth(command.slice(readingIdx)), complete: false, isOption: false, isQuoted: true, + isQuoteContinued: true, }); } else { tokens.push({ token: command.slice(readingIdx), + tokenLength: wcwidth(command.slice(readingIdx)), complete: false, isOption: readingFlag, }); diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index e1f12b8b..1017d65c 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -92,7 +92,7 @@ export const loadLocalSpecsSet = async () => { }; export const getSuggestions = async (cmd: string, cwd: string, shell: Shell): Promise => { - let activeCmd = parseCommand(cmd); + let activeCmd = parseCommand(cmd, shell); const rootToken = activeCmd.at(0); if (activeCmd.length === 0 || !rootToken?.complete) { return; @@ -110,13 +110,10 @@ export const getSuggestions = async (cmd: string, cwd: string, shell: Shell): Pr lastCommand.isPath = true; lastCommand.isPathComplete = pathyComplete; } - const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd); + const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd, shell); if (result == null) return; - let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0; - if (pathy) { - charactersToDrop = pathyComplete ? 0 : path.basename(lastCommand?.token ?? "").length; - } + const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.tokenLength; return { ...result, charactersToDrop }; }; @@ -227,6 +224,7 @@ const runOption = async ( option: Fig.Option, subcommand: Fig.Subcommand, cwd: string, + shell: Shell, persistentOptions: Fig.Option[], acceptedTokens: CommandToken[], ): Promise => { @@ -237,12 +235,13 @@ const runOption = async ( const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token))); if ((option.args instanceof Array && option.args.length > 0) || option.args != null) { const args = option.args instanceof Array ? option.args : [option.args]; - return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false); + return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), true, false); } return runSubcommand( tokens.slice(1), subcommand, cwd, + shell, persistentOptions, acceptedTokens.concat({ ...activeToken, @@ -256,17 +255,18 @@ const runArg = async ( args: Fig.Arg[], subcommand: Fig.Subcommand, cwd: string, + shell: Shell, persistentOptions: Fig.Option[], acceptedTokens: CommandToken[], fromOption: boolean, fromVariadic: boolean, ): Promise => { if (args.length === 0) { - return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption); + return runSubcommand(tokens, subcommand, cwd, shell, persistentOptions, acceptedTokens, true, !fromOption); } else if (tokens.length === 0) { - return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd); + return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd, shell); } else if (!tokens.at(0)?.complete) { - return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd); + return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd, shell); } const activeToken = tokens[0]; @@ -274,20 +274,20 @@ const runArg = async ( if (activeToken.isOption) { const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? [])); if (option != null) { - return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens); + return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens); } return; } const nextSubcommand = await genSubcommand(activeToken.token, subcommand); if (nextSubcommand != null) { - return runSubcommand(tokens.slice(1), nextSubcommand, cwd, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken))); + return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken))); } } const activeArg = args[0]; if (activeArg.isVariadic) { - return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true); + return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true); } else if (activeArg.isCommand) { if (tokens.length <= 0) { return; @@ -296,24 +296,25 @@ const runArg = async ( if (spec == null) return; const subcommand = getSubcommand(spec); if (subcommand == null) return; - return runSubcommand(tokens.slice(1), subcommand, cwd); + return runSubcommand(tokens.slice(1), subcommand, cwd, shell); } - return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false); + return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false); }; const runSubcommand = async ( tokens: CommandToken[], subcommand: Fig.Subcommand, cwd: string, + shell: Shell, persistentOptions: Fig.Option[] = [], acceptedTokens: CommandToken[] = [], argsDepleted = false, argsUsed = false, ): Promise => { if (tokens.length === 0) { - return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd); + return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd, shell); } else if (!tokens.at(0)?.complete) { - return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd); + return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd, shell); } const activeToken = tokens[0]; @@ -323,7 +324,7 @@ const runSubcommand = async ( if (activeToken.isOption) { const option = getOption(activeToken, allOptions); if (option != null) { - return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens); + return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens); } return; } @@ -334,6 +335,7 @@ const runSubcommand = async ( tokens.slice(1), nextSubcommand, cwd, + shell, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)), ); @@ -345,8 +347,8 @@ const runSubcommand = async ( const args = getArgs(subcommand.args); if (args.length != 0) { - return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false); + return runArg(tokens, args, subcommand, cwd, shell, allOptions, acceptedTokens, false, false); } // if the subcommand has no args specified, fallback to the subcommand and ignore this item - return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken)); + return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken)); }; diff --git a/src/runtime/suggestion.ts b/src/runtime/suggestion.ts index 0dd94f00..ef72c6e9 100644 --- a/src/runtime/suggestion.ts +++ b/src/runtime/suggestion.ts @@ -8,6 +8,8 @@ import { runGenerator } from "./generator.js"; import { runTemplates } from "./template.js"; import { Suggestion, SuggestionBlob } from "./model.js"; import log from "../utils/log.js"; +import { escapePath } from "./utils.js"; +import { getPathSeparator, Shell } from "../utils/shell.js"; export enum SuggestionIcons { File = "📄", @@ -200,15 +202,14 @@ const optionSuggestions = ( return filter(validOptions ?? [], filterStrategy, partialCmd, "option"); }; -const getEscapedPath = (value?: string): string | undefined => { - return value?.replaceAll(" ", "\\ "); -}; +function adjustPathSuggestions(suggestions: Suggestion[], partialToken: CommandToken | undefined, shell: Shell): Suggestion[] { + if (partialToken == null) return suggestions; -function adjustPathSuggestions(suggestions: Suggestion[], partialToken?: CommandToken): Suggestion[] { - if (partialToken == null || partialToken.isQuoted) return suggestions; - return suggestions.map((s) => - s.pathy ? { ...s, insertValue: getEscapedPath(s.insertValue), name: s.insertValue == null ? getEscapedPath(s.name)! : s.name } : s, - ); + const sep = getPathSeparator(shell); + return suggestions.map((s) => { + const fullPath = partialToken.isPath ? `${path.dirname(partialToken.token)}${sep}${s.insertValue}` : s.insertValue; + return s.pathy ? { ...s, insertValue: escapePath(fullPath, shell) } : s; + }); } const removeAcceptedSuggestions = (suggestions: Suggestion[], acceptedTokens: CommandToken[]): Suggestion[] => { @@ -239,6 +240,7 @@ export const getSubcommandDrivenRecommendation = async ( argsFromSubcommand: boolean, acceptedTokens: CommandToken[], cwd: string, + shell: Shell, ): Promise => { log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd }); if (argsDepleted && argsFromSubcommand) { @@ -271,6 +273,7 @@ export const getSubcommandDrivenRecommendation = async ( adjustPathSuggestions( suggestions.sort((a, b) => b.priority - a.priority), partialToken, + shell, ), acceptedTokens, ), @@ -287,6 +290,7 @@ export const getArgDrivenRecommendation = async ( acceptedTokens: CommandToken[], variadicArgBound: boolean, cwd: string, + shell: Shell, ): Promise => { let partialCmd = partialToken?.token; if (partialToken?.isPath) { @@ -313,6 +317,7 @@ export const getArgDrivenRecommendation = async ( adjustPathSuggestions( suggestions.sort((a, b) => b.priority - a.priority), partialToken, + shell, ), acceptedTokens, ), diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index bd09ebac..c32fb230 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -6,7 +6,7 @@ import { spawn } from "node:child_process"; import fsAsync from "node:fs/promises"; import { CommandToken } from "./parser.js"; -import { getPathSeperator, gitBashPath, Shell } from "../utils/shell.js"; +import { getPathSeparator, gitBashPath, Shell } from "../utils/shell.js"; import log from "../utils/log.js"; export type ExecuteShellCommandTTYResult = { @@ -27,8 +27,7 @@ const bashSpecialCharacters = /[&|<>\s]/g; const shouldEscapeArg = (arg: string) => { const hasSpecialCharacter = bashSpecialCharacters.test(arg); const isSingleCharacter = arg.length === 1; - const isQuoted = (arg.startsWith(`"`) && arg.endsWith(`"`)) || (arg.startsWith(`'`) && arg.endsWith(`'`)); - return hasSpecialCharacter && !isSingleCharacter && !isQuoted; + return hasSpecialCharacter && !isSingleCharacter && !isQuoted(arg, `"`); }; /* based on libuv process.c used by nodejs, only quotes are escaped for shells. if using git bash need to escape whitespace & special characters in an argument */ @@ -38,6 +37,55 @@ const escapeArgs = (shell: string | undefined, args: string[]) => { return args.map((arg) => (shouldEscapeArg(arg) ? `"${arg.replaceAll('"', '\\"')}"` : arg)); }; +type QuoteChar = `"` | `'` | "`"; + +const isQuoted = (value: string | undefined, quoteChar: QuoteChar): boolean => (value?.startsWith(quoteChar) && value?.endsWith(quoteChar)) ?? false; + +const quoteString = (value: string, quoteChar: QuoteChar): string => { + if (isQuoted(value, quoteChar)) return value; + const escapedValue = value.replaceAll(`\\${quoteChar}`, quoteChar).replaceAll(quoteChar, `\\${quoteChar}`); + return `${quoteChar}${escapedValue}${quoteChar}`; +}; + +const needsQuoted = (value: string, quoteChar: QuoteChar): boolean => isQuoted(value, quoteChar) || value.includes(" "); + +const getShellQuoteChar = (shell: Shell): QuoteChar => { + switch (shell) { + case Shell.Zsh: + case Shell.Bash: + case Shell.Fish: + return `"`; + case Shell.Xonsh: + return `'`; + case Shell.Nushell: + return "`"; + case Shell.Pwsh: + case Shell.Powershell: + return `'`; + case Shell.Cmd: + return `"`; + } +}; + +export const getShellWhitespaceEscapeChar = (shell: Shell): string => { + switch (shell) { + case Shell.Zsh: + case Shell.Bash: + case Shell.Fish: + case Shell.Xonsh: + case Shell.Nushell: + return "\\"; + case Shell.Pwsh: + case Shell.Powershell: + return "`"; + case Shell.Cmd: + return "^"; + } +}; + +export const escapePath = (value: string | undefined, shell: Shell): string | undefined => + value != null && needsQuoted(value, getShellQuoteChar(shell)) ? quoteString(value, getShellQuoteChar(shell)) : value; + export const buildExecuteShellCommand = async (timeout: number): Promise => async ({ command, env, args, cwd }: Fig.ExecuteCommandInput): Promise => { @@ -70,8 +118,9 @@ export const resolveCwd = async ( ): Promise<{ cwd: string; pathy: boolean; complete: boolean }> => { if (cmdToken == null) return { cwd, pathy: false, complete: false }; const { token: rawToken, isQuoted } = cmdToken; - const token = !isQuoted ? rawToken.replaceAll("\\ ", " ") : rawToken; - const sep = getPathSeperator(shell); + const escapedToken = !isQuoted ? rawToken.replaceAll(" ", "\\ ") : rawToken; + const token = escapedToken; + const sep = getPathSeparator(shell); if (!token.includes(sep)) return { cwd, pathy: false, complete: false }; const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token); try { diff --git a/src/tests/runtime/__snapshots__/alias.test.ts.snap b/src/tests/runtime/__snapshots__/alias.test.ts.snap index 5234b1a9..74fe826d 100644 --- a/src/tests/runtime/__snapshots__/alias.test.ts.snap +++ b/src/tests/runtime/__snapshots__/alias.test.ts.snap @@ -6,6 +6,7 @@ exports[`aliasExpand expand on bash aliases 1`] = ` "complete": false, "isOption": false, "token": "glo", + "tokenLength": 3, }, ] `; @@ -16,18 +17,21 @@ exports[`aliasExpand expand on bash aliases 2`] = ` "complete": true, "isOption": false, "token": "echo", + "tokenLength": 4, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "lo", + "tokenLength": 4, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "la", + "tokenLength": 4, }, ] `; @@ -38,6 +42,7 @@ exports[`aliasExpand expand on bash aliases 3`] = ` "complete": true, "isOption": false, "token": "git", + "tokenLength": 3, }, ] `; @@ -48,16 +53,19 @@ exports[`aliasExpand expand on bash aliases 4`] = ` "complete": true, "isOption": false, "token": "ls", + "tokenLength": 2, }, { "complete": true, "isOption": true, "token": "--color", + "tokenLength": 7, }, { "complete": true, "isOption": false, "token": "auto", + "tokenLength": 4, }, ] `; @@ -68,6 +76,7 @@ exports[`aliasExpand expand on zsh aliases 1`] = ` "complete": false, "isOption": false, "token": "glo", + "tokenLength": 3, }, ] `; @@ -78,18 +87,21 @@ exports[`aliasExpand expand on zsh aliases 2`] = ` "complete": true, "isOption": false, "token": "echo", + "tokenLength": 4, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "lo", + "tokenLength": 4, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "la", + "tokenLength": 4, }, ] `; @@ -100,6 +112,7 @@ exports[`aliasExpand expand on zsh aliases 3`] = ` "complete": true, "isOption": false, "token": "git", + "tokenLength": 3, }, ] `; @@ -110,16 +123,19 @@ exports[`aliasExpand expand on zsh aliases 4`] = ` "complete": true, "isOption": false, "token": "ls", + "tokenLength": 2, }, { "complete": true, "isOption": true, "token": "--color", + "tokenLength": 7, }, { "complete": true, "isOption": false, "token": "auto", + "tokenLength": 4, }, ] `; diff --git a/src/tests/runtime/__snapshots__/parser.test.ts.snap b/src/tests/runtime/__snapshots__/parser.test.ts.snap index fe6499f5..1d53b17d 100644 --- a/src/tests/runtime/__snapshots__/parser.test.ts.snap +++ b/src/tests/runtime/__snapshots__/parser.test.ts.snap @@ -1,11 +1,42 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`parseCommand \`cmd1\` 1`] = ` +[ + { + "complete": false, + "isOption": false, + "isQuoted": true, + "token": "cmd1", + "tokenLength": 6, + }, +] +`; + exports[`parseCommand cmd 1`] = ` [ { "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, + }, +] +`; + +exports[`parseCommand cmd "value" 1`] = ` +[ + { + "complete": true, + "isOption": false, + "token": "cmd", + "tokenLength": 3, + }, + { + "complete": true, + "isOption": false, + "isQuoted": true, + "token": "value", + "tokenLength": 7, }, ] `; @@ -16,12 +47,14 @@ exports[`parseCommand cmd "value' 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": false, "isOption": false, "isQuoted": true, "token": "value' ", + "tokenLength": 8, }, ] `; @@ -32,12 +65,14 @@ exports[`parseCommand cmd "value'\\"\\"" 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value'\\"\\"", + "tokenLength": 12, }, ] `; @@ -48,12 +83,14 @@ exports[`parseCommand cmd 'value' 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value", + "tokenLength": 7, }, ] `; @@ -64,16 +101,19 @@ exports[`parseCommand cmd --flag value 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "--flag", + "tokenLength": 6, }, { "complete": false, "isOption": false, "token": "value", + "tokenLength": 5, }, ] `; @@ -84,17 +124,20 @@ exports[`parseCommand cmd --flag="value" 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "--flag", + "tokenLength": 6, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value", + "tokenLength": 7, }, ] `; @@ -105,17 +148,20 @@ exports[`parseCommand cmd --flag='value' 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "--flag", + "tokenLength": 6, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value", + "tokenLength": 7, }, ] `; @@ -126,16 +172,19 @@ exports[`parseCommand cmd --flag=value 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "--flag", + "tokenLength": 6, }, { "complete": false, "isOption": false, "token": "value", + "tokenLength": 5, }, ] `; @@ -146,11 +195,13 @@ exports[`parseCommand cmd -f 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, ] `; @@ -161,17 +212,20 @@ exports[`parseCommand cmd -f 'value' 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value", + "tokenLength": 7, }, ] `; @@ -182,11 +236,13 @@ exports[`parseCommand cmd -f 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": false, "isOption": true, "token": "-f", + "tokenLength": 2, }, ] `; @@ -197,11 +253,13 @@ exports[`parseCommand cmd -f 2`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": false, "isOption": true, "token": "-f", + "tokenLength": 2, }, ] `; @@ -212,16 +270,19 @@ exports[`parseCommand cmd -f value 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, { "complete": true, "isOption": false, "token": "value", + "tokenLength": 5, }, ] `; @@ -232,11 +293,13 @@ exports[`parseCommand cmd -f= 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, ] `; @@ -247,17 +310,20 @@ exports[`parseCommand cmd -f="value" 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, { "complete": true, "isOption": false, "isQuoted": true, "token": "value", + "tokenLength": 7, }, ] `; @@ -268,17 +334,20 @@ exports[`parseCommand cmd -f='val 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, { "complete": false, "isOption": false, "isQuoted": true, "token": "val", + "tokenLength": 4, }, ] `; @@ -289,16 +358,19 @@ exports[`parseCommand cmd -f=value 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": true, "token": "-f", + "tokenLength": 2, }, { "complete": true, "isOption": false, "token": "value", + "tokenLength": 5, }, ] `; @@ -309,6 +381,7 @@ exports[`parseCommand cmd 1`] = ` "complete": false, "isOption": false, "token": "cmd", + "tokenLength": 3, }, ] `; @@ -319,11 +392,13 @@ exports[`parseCommand cmd dir\\ 1/dir\\ 2/item1 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": false, "isOption": false, - "token": "dir\\ 1/dir\\ 2/item1", + "token": "dir 1/dir 2/item1", + "tokenLength": 19, }, ] `; @@ -334,11 +409,83 @@ exports[`parseCommand cmd value 1`] = ` "complete": true, "isOption": false, "token": "cmd", + "tokenLength": 3, }, { "complete": true, "isOption": false, "token": "value", + "tokenLength": 5, + }, +] +`; + +exports[`parseCommand cmd1 "item1"item2 "item3" 1`] = ` +[ + { + "complete": true, + "isOption": false, + "token": "cmd1", + "tokenLength": 4, + }, + { + "complete": true, + "isOption": false, + "isQuoteContinued": true, + "isQuoted": true, + "token": "item1item2", + "tokenLength": 12, + }, + { + "complete": false, + "isOption": false, + "isQuoted": true, + "token": "item3", + "tokenLength": 7, + }, +] +`; + +exports[`parseCommand cmd1 "item1"item2 1`] = ` +[ + { + "complete": true, + "isOption": false, + "token": "cmd1", + "tokenLength": 4, + }, + { + "complete": false, + "isOption": false, + "isQuoteContinued": true, + "isQuoted": true, + "token": "item1item2", + "tokenLength": 12, + }, +] +`; + +exports[`parseCommand cmd1 "item1"item2 item3 1`] = ` +[ + { + "complete": true, + "isOption": false, + "token": "cmd1", + "tokenLength": 4, + }, + { + "complete": true, + "isOption": false, + "isQuoteContinued": true, + "isQuoted": true, + "token": "item1item2", + "tokenLength": 12, + }, + { + "complete": false, + "isOption": false, + "token": "item3", + "tokenLength": 5, }, ] `; @@ -349,6 +496,7 @@ exports[`parseCommand cmd1 | cmd2 1`] = ` "complete": true, "isOption": false, "token": "cmd2", + "tokenLength": 4, }, ] `; @@ -359,11 +507,24 @@ exports[`parseCommand cmd1 - 1`] = ` "complete": true, "isOption": false, "token": "cmd1", + "tokenLength": 4, }, { "complete": false, "isOption": true, "token": "-", + "tokenLength": 1, + }, +] +`; + +exports[`parseCommand 😁 1`] = ` +[ + { + "complete": false, + "isOption": false, + "token": "😁", + "tokenLength": 2, }, ] `; diff --git a/src/tests/runtime/alias.test.ts b/src/tests/runtime/alias.test.ts index 01ed74ae..22b6c8a2 100644 --- a/src/tests/runtime/alias.test.ts +++ b/src/tests/runtime/alias.test.ts @@ -8,6 +8,7 @@ const mockExecuteShellCommand = jest.fn(); jest.unstable_mockModule("../../runtime/utils.js", () => ({ buildExecuteShellCommand: () => mockExecuteShellCommand, + getShellWhitespaceEscapeChar: () => "\\", })); const { aliasExpand, loadAliases } = await import("../../runtime/alias.js"); @@ -27,10 +28,10 @@ alias ls='ls --color=auto'`, }); await loadAliases(Shell.Bash); - expect(aliasExpand([{ token: "glo", complete: false, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "la", complete: true, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "git", complete: true, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "ls", complete: true, isOption: false }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "glo", complete: false, isOption: false, tokenLength: 3 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "la", complete: true, isOption: false, tokenLength: 2 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "git", complete: true, isOption: false, tokenLength: 3 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "ls", complete: true, isOption: false, tokenLength: 2 }])).toMatchSnapshot(); }); test("expand on zsh aliases", async () => { @@ -43,9 +44,9 @@ ls='ls --color=auto'`, }); await loadAliases(Shell.Zsh); - expect(aliasExpand([{ token: "glo", complete: false, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "la", complete: true, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "git", complete: true, isOption: false }])).toMatchSnapshot(); - expect(aliasExpand([{ token: "ls", complete: true, isOption: false }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "glo", complete: false, isOption: false, tokenLength: 3 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "la", complete: true, isOption: false, tokenLength: 2 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "git", complete: true, isOption: false, tokenLength: 3 }])).toMatchSnapshot(); + expect(aliasExpand([{ token: "ls", complete: true, isOption: false, tokenLength: 2 }])).toMatchSnapshot(); }); }); diff --git a/src/tests/runtime/parser.test.ts b/src/tests/runtime/parser.test.ts index ecab7032..ee89193a 100644 --- a/src/tests/runtime/parser.test.ts +++ b/src/tests/runtime/parser.test.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { parseCommand } from "../../runtime/parser.js"; +import { Shell } from "../../utils/shell.js"; const testData = [ { command: `cmd --flag value` }, @@ -22,16 +23,22 @@ const testData = [ { command: `cmd` }, { command: `cmd ` }, { command: `cmd "value' ` }, + { command: `cmd "value" ` }, { command: `cmd "value'\\"\\"" ` }, { command: `cmd1 | cmd2 ` }, { command: `cmd1 -` }, { command: `cmd dir\\ 1/dir\\ 2/item1` }, + { command: `cmd1 "item1"item2` }, + { command: `cmd1 "item1"item2 item3` }, + { command: `cmd1 "item1"item2 "item3"` }, + { command: "`cmd1`" }, + { command: "😁" }, ]; describe(`parseCommand`, () => { testData.forEach(({ command }) => { test(command, () => { - expect(parseCommand(command)).toMatchSnapshot(); + expect(parseCommand(command, Shell.Bash)).toMatchSnapshot(); }); }); }); diff --git a/src/utils/shell.ts b/src/utils/shell.ts index adac7c73..212a58c6 100644 --- a/src/utils/shell.ts +++ b/src/utils/shell.ts @@ -58,7 +58,7 @@ export const setupZshDotfiles = async () => { await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin")); }; -const findPareentProcess = async () => { +const findParentProcess = async () => { try { return (await find("pid", process.ppid)).at(0); } catch (e) { @@ -90,7 +90,7 @@ export const inferShell = async () => { } // try getting shell from parent process - const processResult = await findPareentProcess(); + const processResult = await findParentProcess(); const name = processResult?.name; return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined; }; @@ -143,7 +143,7 @@ const getGitBashPaths = async (): Promise => { export const getBackspaceSequence = (press: KeyPressEvent, shell: Shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence; -export const getPathSeperator = (shell: Shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep); +export const getPathSeparator = (shell: Shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep); // nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line export const getShellPromptRewrites = (shell: Shell) => shell == Shell.Nushell;