diff --git a/__snapshots__/PuppeteerReplayStringifyExtension.test.ts.js b/__snapshots__/PuppeteerReplayStringifyExtension.test.ts.js index 83fb6f0c..58accb63 100644 --- a/__snapshots__/PuppeteerReplayStringifyExtension.test.ts.js +++ b/__snapshots__/PuppeteerReplayStringifyExtension.test.ts.js @@ -48,6 +48,6 @@ export async function run(extension) { if (process && import.meta.url === url.pathToFileURL(process.argv[1]).href) { run() } -//# recorderSourceMap=CQc +//# recorderSourceMap=BIO `; diff --git a/__snapshots__/lighthouse.test.ts.js b/__snapshots__/lighthouse.test.ts.js index 7ecebf6b..1ee0a9b3 100644 --- a/__snapshots__/lighthouse.test.ts.js +++ b/__snapshots__/lighthouse.test.ts.js @@ -258,6 +258,6 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CaIiBMuBaoCYgDiB +//# recorderSourceMap=BNERGXNkBMwBR `; diff --git a/__snapshots__/stringify.test.ts.js b/__snapshots__/stringify.test.ts.js index 90d168c8..3f5cc926 100644 --- a/__snapshots__/stringify.test.ts.js +++ b/__snapshots__/stringify.test.ts.js @@ -201,7 +201,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQI +//# recorderSourceMap=BIE `; @@ -415,7 +415,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQS +//# recorderSourceMap=BIJ `; @@ -633,7 +633,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQa +//# recorderSourceMap=BIN `; @@ -850,7 +850,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQc +//# recorderSourceMap=BIO `; @@ -1069,7 +1069,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQc +//# recorderSourceMap=BIO `; @@ -1276,7 +1276,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQI +//# recorderSourceMap=BIE `; @@ -1483,7 +1483,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQI +//# recorderSourceMap=BIE `; @@ -1696,7 +1696,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQMcI +//# recorderSourceMap=BIGOE `; @@ -1905,7 +1905,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQI +//# recorderSourceMap=BIE `; @@ -2114,7 +2114,7 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later console.error(err); process.exit(1); }); -//# recorderSourceMap=CQI +//# recorderSourceMap=BIE `; diff --git a/package-lock.json b/package-lock.json index bb489702..14a76756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "cli-table3": "0.6.3", "colorette": "2.0.19", - "vlq": "2.0.4", "yargs": "17.6.1" }, "bin": { @@ -4553,11 +4552,6 @@ "node": ">=0.8.0" } }, - "node_modules/vlq": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", - "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" - }, "node_modules/vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", @@ -8120,11 +8114,6 @@ } } }, - "vlq": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", - "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" - }, "vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", diff --git a/package.json b/package.json index a2e9db7e..15b1f401 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "dependencies": { "cli-table3": "0.6.3", "colorette": "2.0.19", - "vlq": "2.0.4", "yargs": "17.6.1" } } diff --git a/src/@types/vlq/index.d.ts b/src/@types/vlq/index.d.ts deleted file mode 100644 index 62c1070c..00000000 --- a/src/@types/vlq/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'vlq' { - export function encode(data: number[]): string; - export function decode(str: string): number[]; -} diff --git a/src/stringify.ts b/src/stringify.ts index 300a7944..8cd0cbe5 100644 --- a/src/stringify.ts +++ b/src/stringify.ts @@ -19,7 +19,7 @@ import { LineWriter } from './LineWriter.js'; import { PuppeteerStringifyExtension } from './PuppeteerStringifyExtension.js'; import type { Step, UserFlow } from './Schema.js'; import { StringifyExtension } from './StringifyExtension.js'; -import * as vlq from 'vlq'; +import { decode, encode } from './vlq.js'; export interface StringifyOptions { extension?: StringifyExtension; @@ -54,7 +54,7 @@ export async function stringify( await ext.beforeAllSteps?.(out, flow); - const sourceMap: Array = [1]; + const sourceMap: Array = [1]; // The first int indicates the version. for (const step of flow.steps) { const firstLine = out.getSize(); await ext.beforeEachStep?.(out, step, flow); @@ -65,7 +65,7 @@ export async function stringify( } await ext.afterAllSteps?.(out, flow); - out.appendLine(SOURCE_MAP_PREFIX + vlq.encode(sourceMap)); + out.appendLine(SOURCE_MAP_PREFIX + encode(sourceMap)); return out.toString(); } @@ -107,7 +107,7 @@ export function parseSourceMap(text: string): SourceMap | undefined { for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i] as string; if (line.trim().startsWith(SOURCE_MAP_PREFIX)) { - return vlq.decode(line.trim().substring(SOURCE_MAP_PREFIX.length)); + return decode(line.trim().substring(SOURCE_MAP_PREFIX.length)); } } return; diff --git a/src/vlq.ts b/src/vlq.ts new file mode 100644 index 00000000..7ce994f0 --- /dev/null +++ b/src/vlq.ts @@ -0,0 +1,82 @@ +/** + Copyright 2022 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +const alpha = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +const charToIdx = alpha.split('').reduce((acc, char, idx) => { + acc.set(char, idx); + return acc; +}, new Map()); + +const LEAST_5_BIT_MASK = 0b011111; +const CONTINUATION_BIT_MASK = 0b100000; +const MAX_INT = 2147483647; + +/** + * Encoding variable length integer into base64 (6-bit): + * + * 1 N N N N N | 0 N N N N N + * + * The first bit indicates if there is more data for the int. + */ +export function encodeInt(num: number) { + if (num < 0) { + throw new Error('Only postive integers and zero are supported'); + } + if (num > MAX_INT) { + throw new Error( + 'Only integers between 0 and ' + MAX_INT + ' are supported' + ); + } + const result = []; + do { + let payload = num & LEAST_5_BIT_MASK; + num >>>= 5; + if (num > 0) payload |= CONTINUATION_BIT_MASK; + result.push(alpha[payload]); + } while (num !== 0); + return result.join(''); +} + +export function encode(nums: number[]): string { + const parts = []; + for (const num of nums) { + parts.push(encodeInt(num)); + } + return parts.join(''); +} + +export function decode(str: string) { + const results = []; + const chrs = str.split(''); + + let result = 0; + let shift = 0; + for (const ch of chrs) { + const num = charToIdx.get(ch); + result |= (num & LEAST_5_BIT_MASK) << shift; + shift += 5; + const hasMore = num & CONTINUATION_BIT_MASK; + if (!hasMore) { + results.push(result); + result = 0; + shift = 0; + } + } + + return results; +} diff --git a/test/stringify.test.ts b/test/stringify.test.ts index 989904c3..8b5d54ab 100644 --- a/test/stringify.test.ts +++ b/test/stringify.test.ts @@ -20,7 +20,7 @@ import { StringifyExtension } from '../src/StringifyExtension.js'; import { Step, StepType, UserFlow } from '../src/Schema.js'; import { LineWriter } from '../src/LineWriter.js'; import snapshot from 'snap-shot-it'; -import * as vlq from 'vlq'; +import { decode } from '../src/vlq.js'; describe('stringify', () => { it('should print the correct script for a navigate step', async () => { @@ -250,7 +250,7 @@ describe('stringify', () => { 'stringifyStep0', 'afterStep0', 'afterAll', - '//# recorderSourceMap=CCG', + '//# recorderSourceMap=BBD', '', ].join('\n') ); @@ -272,7 +272,7 @@ describe('stringify', () => { .reverse() .find((line) => line.trim() !== ''); snapshot( - vlq.decode(sourceMapLine?.split('//# recorderSourceMap=').pop() as string) + decode(sourceMapLine?.split('//# recorderSourceMap=').pop() as string) ); }); @@ -281,7 +281,7 @@ describe('stringify', () => { test test test - //# recorderSourceMap=CCG + //# recorderSourceMap=BBD `); assert.deepStrictEqual(sourceMap, [1, 1, 3]); }); diff --git a/test/vlq.test.ts b/test/vlq.test.ts new file mode 100644 index 00000000..2155eac3 --- /dev/null +++ b/test/vlq.test.ts @@ -0,0 +1,41 @@ +/** + Copyright 2022 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { encodeInt, decode, encode } from '../src/vlq.js'; +import { assert } from 'chai'; + +describe('vlq', () => { + it('should encode', () => { + assert.strictEqual(encodeInt(0), 'A'); + assert.strictEqual(encodeInt(1), 'B'); + assert.strictEqual(encodeInt(123), '7D'); + assert.strictEqual(encodeInt(123) + encodeInt(123456789), '7D1oz31D'); + assert.strictEqual(encodeInt(123456789), '1oz31D'); + assert.strictEqual(encodeInt(2147483647), '//////B'); + }); + it('should decode', () => { + assert.deepStrictEqual(decode('A'), [0]); + assert.deepStrictEqual(decode('C'), [2]); + assert.deepStrictEqual(decode('D'), [3]); + assert.deepStrictEqual(decode('7D'), [123]); + assert.deepStrictEqual(decode('1oz31D'), [123456789]); + assert.deepStrictEqual(decode('7D1oz31D'), [123, 123456789]); + assert.deepStrictEqual(decode('//////B'), [2147483647]); + }); + it('should encode array', () => { + assert.strictEqual(encode([0, 1, 123]), 'AB7D'); + }); +}); diff --git a/tsconfig.base.json b/tsconfig.base.json index 19b8eaba..29f5fe70 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,7 +24,6 @@ "strictNullChecks": true, "strictPropertyInitialization": true, "target": "ES2019", - "useUnknownInCatchVariables": true, - "typeRoots": ["./node_modules/@types", "./src/@types"] + "useUnknownInCatchVariables": true } }