diff --git a/.gitignore b/.gitignore index 141006a60..f8d9292fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ coverage node_modules -lib +libesm +libcjs dist yarn-error.log .vscode diff --git a/.npmignore b/.npmignore index 46fd394a5..23d3bc8fa 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,10 @@ .babelrc -.eslintrc +.eslint.config.mjs .gitignore .npmignore .vscode .nyc_output +test-d components coverage examples @@ -15,5 +16,6 @@ runtime.js src tasks test +tsconfig.json yarn-error.log yarn.lock diff --git a/README.md b/README.md index e14adb8a3..8d650fc38 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,29 @@ Based on the algorithm proposed in npm install diff --save ``` -## Usage +## Getting started -Broadly, jsdiff's diff functions all take an old text and a new text and perform three steps: +### Imports + +In an environment where you can use imports, everything you need can be imported directly from `diff`. e.g. + +ESM: + +``` +import {diffChars, createPatch} from 'diff'; +``` + +CommonJS + +``` +const {diffChars, createPatch} = require('diff'); +``` + +If you want to serve jsdiff to a web page without using a module system, you can use `dist/diff.js` or `dist/diff.min.js`. These create a global called `Diff` that contains the entire JsDiff API as its properties. + +### Usage + +jsdiff's diff functions all take an old text and a new text and perform three steps: 1. Split both texts into arrays of "tokens". What constitutes a token varies; in `diffChars`, each character is a token, while in `diffLines`, each line is a token. @@ -22,9 +42,9 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform 3. Return an array representing the transformation computed in the previous step as a series of [change objects](#change-objects). The array is ordered from the start of the input to the end, and each change object represents *inserting* one or more tokens, *deleting* one or more tokens, or *keeping* one or more tokens. -### API +## API -* `Diff.diffChars(oldStr, newStr[, options])` - diffs two blocks of text, treating each character as a token. +* `diffChars(oldStr, newStr[, options])` - diffs two blocks of text, treating each character as a token. ("Characters" here means Unicode code points - the elements you get when you loop over a string with a `for ... of ...` loop.) @@ -33,7 +53,7 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform Options * `ignoreCase`: If `true`, the uppercase and lowercase forms of a character are considered equal. Defaults to `false`. -* `Diff.diffWords(oldStr, newStr[, options])` - diffs two blocks of text, treating each word and each punctuation mark as a token. Whitespace is ignored when computing the diff (but preserved as far as possible in the final change objects). +* `diffWords(oldStr, newStr[, options])` - diffs two blocks of text, treating each word and each punctuation mark as a token. Whitespace is ignored when computing the diff (but preserved as far as possible in the final change objects). Returns a list of [change objects](#change-objects). @@ -45,9 +65,9 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform Using an `Intl.Segmenter` should allow better word-level diffing of non-English text than the default behaviour. For instance, `Intl.Segmenter`s can generally identify via built-in dictionaries which sequences of adjacent Chinese characters form words, allowing word-level diffing of Chinese. By specifying a language when instantiating the segmenter (e.g. `new Intl.Segmenter('sv', {granularity: 'word'})`) you can also support language-specific rules, like treating Swedish's colon separated contractions (like *k:a* for *kyrka*) as single words; by default this would be seen as two words separated by a colon. -* `Diff.diffWordsWithSpace(oldStr, newStr[, options])` - diffs two blocks of text, treating each word, punctuation mark, newline, or run of (non-newline) whitespace as a token. +* `diffWordsWithSpace(oldStr, newStr[, options])` - diffs two blocks of text, treating each word, punctuation mark, newline, or run of (non-newline) whitespace as a token. -* `Diff.diffLines(oldStr, newStr[, options])` - diffs two blocks of text, treating each line as a token. +* `diffLines(oldStr, newStr[, options])` - diffs two blocks of text, treating each line as a token. Options * `ignoreWhitespace`: `true` to ignore leading and trailing whitespace characters when checking if two lines are equal. Defaults to `false`. @@ -60,17 +80,17 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform Returns a list of [change objects](#change-objects). -* `Diff.diffSentences(oldStr, newStr[, options])` - diffs two blocks of text, treating each sentence, and the whitespace between each pair of sentences, as a token. The characters `.`, `!`, and `?`, when followed by whitespace, are treated as marking the end of a sentence; nothing else besides the end of the string is considered to mark a sentence end. +* `diffSentences(oldStr, newStr[, options])` - diffs two blocks of text, treating each sentence, and the whitespace between each pair of sentences, as a token. The characters `.`, `!`, and `?`, when followed by whitespace, are treated as marking the end of a sentence; nothing else besides the end of the string is considered to mark a sentence end. - (For more sophisticated detection of sentence breaks, including support for non-English punctuation, consider instead tokenizing with an [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) with `granularity: 'sentence'` and passing the result to `Diff.diffArrays`.) + (For more sophisticated detection of sentence breaks, including support for non-English punctuation, consider instead tokenizing with an [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) with `granularity: 'sentence'` and passing the result to `diffArrays`.) Returns a list of [change objects](#change-objects). -* `Diff.diffCss(oldStr, newStr[, options])` - diffs two blocks of text, comparing CSS tokens. +* `diffCss(oldStr, newStr[, options])` - diffs two blocks of text, comparing CSS tokens. Returns a list of [change objects](#change-objects). -* `Diff.diffJson(oldObj, newObj[, options])` - diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token. Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result. +* `diffJson(oldObj, newObj[, options])` - diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token. Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result. Returns a list of [change objects](#change-objects). @@ -78,14 +98,14 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform * `stringifyReplacer`: A custom replacer function. Operates similarly to the `replacer` parameter to [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter), but must be a function. * `undefinedReplacement`: A value to replace `undefined` with. Ignored if a `stringifyReplacer` is provided. -* `Diff.diffArrays(oldArr, newArr[, options])` - diffs two arrays of tokens, comparing each item for strict equality (===). +* `diffArrays(oldArr, newArr[, options])` - diffs two arrays of tokens, comparing each item for strict equality (===). Options * `comparator`: `function(left, right)` for custom equality checks Returns a list of [change objects](#change-objects). -* `Diff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format. +* `createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format. Parameters: * `oldFileName` : String to be output in the filename section of the patch for the removals @@ -99,15 +119,15 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform - `ignoreWhitespace`: Same as in `diffLines`. Defaults to `false`. - `stripTrailingCr`: Same as in `diffLines`. Defaults to `false`. -* `Diff.createPatch(fileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch. +* `createPatch(fileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - creates a unified diff patch. - Just like Diff.createTwoFilesPatch, but with oldFileName being equal to newFileName. + Just like createTwoFilesPatch, but with oldFileName being equal to newFileName. -* `Diff.formatPatch(patch)` - creates a unified diff patch. +* `formatPatch(patch)` - creates a unified diff patch. `patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). -* `Diff.structuredPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - returns an object with an array of hunk objects. +* `structuredPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]])` - returns an object with an array of hunk objects. This method is similar to createTwoFilesPatch, but returns a data structure suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: @@ -123,7 +143,7 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform } ``` -* `Diff.applyPatch(source, patch[, options])` - attempts to apply a unified diff patch. +* `applyPatch(source, patch[, options])` - attempts to apply a unified diff patch. Hunks are applied first to last. `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly. If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly. If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match. Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly. @@ -143,7 +163,7 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform - `autoConvertLineEndings`: If `true`, and if the file to be patched consistently uses different line endings to the patch (i.e. either the file always uses Unix line endings while the patch uses Windows ones, or vice versa), then `applyPatch` will behave as if the line endings in the patch were the same as those in the source file. (If `false`, the patch will usually fail to apply in such circumstances since lines deleted in the patch won't be considered to match those in the source file.) Defaults to `true`. - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overridden to provide fuzzier comparison. Should return false if the lines should be rejected. -* `Diff.applyPatches(patch, options)` - applies one or more patches. +* `applyPatches(patch, options)` - applies one or more patches. `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files). @@ -154,17 +174,17 @@ Broadly, jsdiff's diff functions all take an old text and a new text and perform Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made. -* `Diff.parsePatch(diffStr)` - Parses a patch into structured data +* `parsePatch(diffStr)` - Parses a patch into structured data - Return a JSON object representation of the a patch, suitable for use with the `applyPatch` method. This parses to the same structure returned by `Diff.structuredPatch`. + Return a JSON object representation of the a patch, suitable for use with the `applyPatch` method. This parses to the same structure returned by `structuredPatch`. -* `Diff.reversePatch(patch)` - Returns a new structured patch which when applied will undo the original `patch`. +* `reversePatch(patch)` - Returns a new structured patch which when applied will undo the original `patch`. `patch` may be either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). -* `Diff.convertChangesToXML(changes)` - converts a list of change objects to a serialized XML format +* `convertChangesToXML(changes)` - converts a list of change objects to a serialized XML format -* `Diff.convertChangesToDMP(changes)` - converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library +* `convertChangesToDMP(changes)` - converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library #### Universal `options` @@ -188,14 +208,14 @@ The simplest way to customize tokenization behavior is to simply tokenize the te To customize the notion of token equality used, use the `comparator` option to `diffArrays`. -For even more customisation of the diffing behavior, you can create a `new Diff.Diff()` object, overwrite its `castInput`, `tokenize`, `removeEmpty`, `equals`, and `join` properties with your own functions, then call its `diff(oldString, newString[, options])` method. The methods you can overwrite are used as follows: +For even more customisation of the diffing behavior, you can extend the `Diff()` class, override its `castInput`, `tokenize`, `removeEmpty`, `equals`, and `join` properties with your own functions, then call its `diff(oldString, newString[, options])` method. The methods you can override are used as follows: * `castInput(value, options)`: used to transform the `oldString` and `newString` before any other steps in the diffing algorithm happen. For instance, `diffJson` uses `castInput` to serialize the objects being diffed to JSON. Defaults to a no-op. * `tokenize(value, options)`: used to convert each of `oldString` and `newString` (after they've gone through `castInput`) to an array of tokens. Defaults to returning `value.split('')` (returning an array of individual characters). * `removeEmpty(array)`: called on the arrays of tokens returned by `tokenize` and can be used to modify them. Defaults to stripping out falsey tokens, such as empty strings. `diffArrays` overrides this to simply return the `array`, which means that falsey values like empty strings can be handled like any other token by `diffArrays`. * `equals(left, right, options)`: called to determine if two tokens (one from the old string, one from the new string) should be considered equal. Defaults to comparing them with `===`. -* `join(tokens)`: gets called with an array of consecutive tokens that have either all been added, all been removed, or are all common. Needs to join them into a single value that can be used as the `value` property of the [change object](#change-objects) for these tokens. Defaults to simply returning `tokens.join('')`. -* `postProcess(changeObjects)`: gets called at the end of the algorithm with the [change objects](#change-objects) produced, and can do final cleanups on them. Defaults to simply returning `changeObjects` unchanged. +* `join(tokens)`: gets called with an array of consecutive tokens that have either all been added, all been removed, or are all common. Needs to join them into a single value that can be used as the `value` property of the [change object](#change-objects) for these tokens. Defaults to simply returning `tokens.join('')` (and therefore by default will error out if your tokens are not strings; differs that support non-string tokens like `diffArrays` should override it to be a no-op to fix this). +* `postProcess(changeObjects, options)`: gets called at the end of the algorithm with the [change objects](#change-objects) produced, and can do final cleanups on them. Defaults to simply returning `changeObjects` unchanged. ### Change Objects Many of the methods above return change objects. These objects consist of the following fields: @@ -213,12 +233,12 @@ Many of the methods above return change objects. These objects consist of the fo ```js require('colors'); -const Diff = require('diff'); +const {diffChars} = require('diff'); const one = 'beep boop'; const other = 'beep boob blah'; -const diff = Diff.diffChars(one, other); +const diff = diffChars(one, other); diff.forEach((part) => { // green for additions, red for deletions @@ -275,10 +295,10 @@ Open the above .html file in a browser and you should see The code below is roughly equivalent to the Unix command `diff -u file1.txt file2.txt > mydiff.patch`: ``` -const Diff = require('diff'); +const {createTwoFilesPatch} = require('diff'); const file1Contents = fs.readFileSync("file1.txt").toString(); const file2Contents = fs.readFileSync("file2.txt").toString(); -const patch = Diff.createTwoFilesPatch("file1.txt", "file2.txt", file1Contents, file2Contents); +const patch = createTwoFilesPatch("file1.txt", "file2.txt", file1Contents, file2Contents); fs.writeFileSync("mydiff.patch", patch); ``` @@ -289,10 +309,10 @@ fs.writeFileSync("mydiff.patch", patch); The code below is roughly equivalent to the Unix command `patch file1.txt mydiff.patch`: ``` -const Diff = require('diff'); +const {applyPatch} = require('diff'); const file1Contents = fs.readFileSync("file1.txt").toString(); const patch = fs.readFileSync("mydiff.patch").toString(); -const patchedFile = Diff.applyPatch(file1Contents, patch); +const patchedFile = applyPatch(file1Contents, patch); fs.writeFileSync("file1.txt", patchedFile); ``` @@ -301,9 +321,9 @@ fs.writeFileSync("file1.txt", patchedFile); The code below is roughly equivalent to the Unix command `patch < mydiff.patch`: ``` -const Diff = require('diff'); +const {applyPatches} = require('diff'); const patch = fs.readFileSync("mydiff.patch").toString(); -Diff.applyPatches(patch, { +applyPatches(patch, { loadFile: (patch, callback) => { let fileContents; try { @@ -332,7 +352,19 @@ Diff.applyPatches(patch, { ## Compatibility -jsdiff supports all ES3 environments with some known issues on IE8 and below. Under these browsers some diff algorithms such as word diff and others may fail due to lack of support for capturing groups in the `split` operation. +jsdiff should support all ES5 environments. If you find one that it doesn't support, please [open an issue](https://github.com/kpdecker/jsdiff/issues). + +## TypeScript + +As of version 8, JsDiff ships with type definitions. From version 8 onwards, you should not depend on the `@types/diff` package. + +One tricky pattern pervades the type definitions and is worth explaining here. Most diff-generating and patch-generating functions (`diffChars`, `diffWords`, `structuredPatch`, etc) can be run in async mode (by providing a `callback` option), in abortable mode (by passing a `timeout` or `maxEditLength` property), or both. This is awkward for typing, because these modes have different call signatures: + * in abortable mode, the result *might* be `undefined`, and + * in async mode, the result (which *might* be allowed to be `undefined`, depending upon whether we're in abortable mode) is passed to the provide callback instead of being returned, and the return value is always `undefined` + +Our type definitions handle this as best they can by declaring different types for multiple [overload signatures](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads) for each such function - and also by declaring different types for abortable and nonabortable options objects. For instance, an object of type `DiffCharsOptionsAbortable` is valid to pass as the `options` argument to `diffChars` and represents an *abortable* call (whose result may be `undefined`) since it necessarily contains either the `timeout` or `maxEditLength` property. + +This approach, while probably the least bad way available to add types to JsDiff without radically refactoring the library's API, does not yield perfect results. *As long as* TypeScript is able to statically determine the type of your options, and therefore which overload signature is appropriate, everything should work fine. This should always be the case if you are passing an object literal as the `options` argument and inlining the definition of any `callback` function within that literal. But in cases where TypeScript *cannot* manage to do this - as may often be the case if you, say, define an `options: any = {}` object, build up your options programmatically, and then pass the result to a JsDiff function - then it is likely to fail to match the correct overload signature (probably defaulting to assuming you are calling the function in non-abortable, non-async mode), potentially causing type errors. You can either ignore (e.g. with `@ts-expect-error`) any such errors, or try to avoid them by refactoring your code so that TypeScript can always statically determine the type of the options you pass. ## License diff --git a/eslint.config.mjs b/eslint.config.mjs index ddc7551c8..ea1c73566 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,176 +1,182 @@ -import globals from "globals"; -import babelParser from "@babel/eslint-parser"; - -export default [ - { - languageOptions: { - globals: { - ...globals.browser, - }, - - parser: babelParser, - }, - - rules: { - // Possible Errors // - //-----------------// - "comma-dangle": [2, "never"], - "no-cond-assign": [2, "except-parens"], - "no-console": 1, // Allow for debugging - "no-constant-condition": 2, - "no-control-regex": 2, - "no-debugger": 1, // Allow for debugging - "no-dupe-args": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-extra-semi": 2, - "no-func-assign": 2, - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-regex-spaces": 2, - "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging - "use-isnan": 2, - "valid-typeof": 2, - - // Best Practices // - //----------------// - curly: 2, - "default-case": 1, - - "dot-notation": [2, { - allowKeywords: false, - }], - - "guard-for-in": 1, - "no-alert": 2, - "no-caller": 2, - "no-div-regex": 1, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-implied-eval": 2, - "no-iterator": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-multi-spaces": 2, - "no-multi-str": 1, - "no-native-reassign": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-wrappers": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-process-env": 2, - "no-proto": 2, - "no-redeclare": 2, - "no-return-assign": 2, - "no-script-url": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-throw-literal": 2, - "no-unused-expressions": 2, - "no-warning-comments": 1, - "no-with": 2, - radix: 2, - "wrap-iife": 2, - - // Variables // - //-----------// - "no-catch-shadow": 2, - "no-delete-var": 2, - "no-label-var": 2, - "no-undef": 2, - "no-undef-init": 2, - - "no-unused-vars": [2, { - vars: "all", - args: "after-used", - }], - - "no-use-before-define": [2, "nofunc"], - - // Node.js // - //---------// - - // Stylistic // - //-----------// - "brace-style": [2, "1tbs", { - allowSingleLine: true, - }], - - camelcase: 2, - - "comma-spacing": [2, { - before: false, - after: true, - }], +// @ts-check - "comma-style": [2, "last"], - "consistent-this": [1, "self"], - "eol-last": 2, - "func-style": [2, "declaration"], - - "key-spacing": [2, { - beforeColon: false, - afterColon: true, - }], - - "new-cap": 2, - "new-parens": 2, - "no-array-constructor": 2, - "no-lonely-if": 2, - "no-mixed-spaces-and-tabs": 2, - "no-nested-ternary": 1, - "no-new-object": 2, - "no-spaced-func": 2, - "no-trailing-spaces": 2, - - "quote-props": [2, "as-needed", { - keywords: true, - }], - - quotes: [2, "single", "avoid-escape"], - semi: 2, - - "semi-spacing": [2, { - before: false, - after: true, - }], - - "space-before-blocks": [2, "always"], - - "space-before-function-paren": [2, { - anonymous: "never", - named: "never", - }], +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import globals from "globals"; - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-unary-ops": 2, - "spaced-comment": [2, "always"], - "wrap-regex": 1, - "no-var": 2, - }, +export default tseslint.config( + { + ignores: [ + "**/*", // ignore everything... + "!src/**/", "!src/**/*.ts", // ... except our TypeScript source files... + "!test/**/", "!test/**/*.js", // ... and our tests + ], + }, + eslint.configs.recommended, + tseslint.configs.recommended, + { + files: ['src/**/*.ts'], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, - { - files: ['test/**/*.js'], - languageOptions: { - globals: { - ...globals.node, - ...globals.mocha, - }, - }, - rules: { - "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax - }, + extends: [tseslint.configs.recommendedTypeChecked], + rules: { + // Not sure if these actually serve a purpose, but they provide a way to enforce SOME of what + // would be imposed by having "verbatimModuleSyntax": true in our tsconfig.json without + // actually doing that. + "@typescript-eslint/consistent-type-imports": 2, + "@typescript-eslint/consistent-type-exports": 2, + + // Things from the recommendedTypeChecked shared config that are disabled simply because they + // caused lots of errors in our existing code when tried. Plausibly useful to turn on if + // possible and somebody fancies doing the work: + "@typescript-eslint/no-unsafe-argument": 0, + "@typescript-eslint/no-unsafe-assignment": 0, + "@typescript-eslint/no-unsafe-call": 0, + "@typescript-eslint/no-unsafe-member-access": 0, + "@typescript-eslint/no-unsafe-return": 0, } -]; \ No newline at end of file + }, + { + languageOptions: { + globals: { + ...globals.browser, + }, + }, + + rules: { + // Possible Errors // + //-----------------// + "comma-dangle": [2, "never"], + "no-console": 1, // Allow for debugging + "no-debugger": 1, // Allow for debugging + "no-extra-parens": [2, "functions"], + "no-extra-semi": 2, + "no-negated-in-lhs": 2, + "no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging + + // Best Practices // + //----------------// + curly: 2, + "default-case": 1, + "dot-notation": [2, { + allowKeywords: false, + }], + "guard-for-in": 1, + "no-alert": 2, + "no-caller": 2, + "no-div-regex": 1, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-multi-spaces": 2, + "no-multi-str": 1, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-process-env": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-throw-literal": 2, + "no-unused-expressions": 2, + "no-warning-comments": 1, + radix: 2, + "wrap-iife": 2, + + // Variables // + //-----------// + "no-catch-shadow": 2, + "no-label-var": 2, + "no-undef-init": 2, + + // Node.js // + //---------// + + // Stylistic // + //-----------// + "brace-style": [2, "1tbs", { + allowSingleLine: true, + }], + camelcase: 2, + "comma-spacing": [2, { + before: false, + after: true, + }], + "comma-style": [2, "last"], + "consistent-this": [1, "self"], + "eol-last": 2, + "func-style": [2, "declaration"], + "key-spacing": [2, { + beforeColon: false, + afterColon: true, + }], + "new-cap": 2, + "new-parens": 2, + "no-array-constructor": 2, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-nested-ternary": 1, + "no-new-object": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 2, + "quote-props": [2, "as-needed", { + keywords: true, + }], + quotes: [2, "single", "avoid-escape"], + semi: 2, + "semi-spacing": [2, { + before: false, + after: true, + }], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, { + anonymous: "never", + named: "never", + }], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": 2, + "spaced-comment": [2, "always"], + "wrap-regex": 1, + "no-var": 2, + + // Typescript // + //------------// + "@typescript-eslint/no-explicit-any": 0, // Very strict rule, incompatible with our code + + // We use these intentionally - e.g. + // export interface DiffCssOptions extends CommonDiffOptions {} + // for the options argument to diffCss which currently takes no options beyond the ones + // common to all diffFoo functions. Doing this allows consistency (one options interface per + // diffFoo function) and future-proofs against the API having to change in future if we add a + // non-common option to one of these functions. + "@typescript-eslint/no-empty-object-type": [2, {allowInterfaces: 'with-single-extends'}], + }, + }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + ...globals.node, + ...globals.mocha, + }, + }, + rules: { + "no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax + "@typescript-eslint/no-unused-expressions": 0, // (as above) + }, + } +); diff --git a/examples/node_example.js b/examples/node_example.js index ad7633730..d928019e1 100644 --- a/examples/node_example.js +++ b/examples/node_example.js @@ -1,12 +1,12 @@ require('colors'); -let Diff = require('../'); +const {diffChars} = require('diff'); -let one = 'beep boop'; -let other = 'beep boob blah'; +const one = 'beep boop'; +const other = 'beep boob blah'; -let diff = Diff.diffChars(one, other); +const diff = diffChars(one, other); -diff.forEach(function(part) { +diff.forEach((part) => { // green for additions, red for deletions let text = part.added ? part.value.bgGreen : part.removed ? part.value.bgRed : diff --git a/karma.conf.js b/karma.conf.js index 1ddac096e..82c09b403 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,6 +1,4 @@ -/* eslint-env node */ -/* eslint-disable no-var, camelcase */ -module.exports = function(config) { +export default function(config) { config.set({ basePath: '', diff --git a/package.json b/package.json index 8699cf55e..338c6abc7 100644 --- a/package.json +++ b/package.json @@ -28,42 +28,72 @@ "engines": { "node": ">=0.3.1" }, - "main": "./lib/index.js", - "module": "./lib/index.es6.js", + "main": "./libcjs/index.js", + "module": "./libesm/index.js", "browser": "./dist/diff.js", "unpkg": "./dist/diff.js", "exports": { ".": { - "import": "./lib/index.mjs", - "require": "./lib/index.js" + "import": { + "types": "./libesm/index.d.ts", + "default": "./libesm/index.js" + }, + "require": { + "types": "./libcjs/index.d.ts", + "default": "./libcjs/index.js" + } }, "./package.json": "./package.json", - "./": "./", - "./*": "./*" + "./lib/*.js": { + "import": { + "types": "./libesm/*.d.ts", + "default": "./libesm/*.js" + }, + "require": { + "types": "./libcjs/*.d.ts", + "default": "./libcjs/*.js" + } + }, + "./lib/": { + "import": { + "types": "./libesm/", + "default": "./libesm/" + }, + "require": { + "types": "./libcjs/", + "default": "./libcjs/" + } + } }, + "type": "module", + "types": "libcjs/index.d.ts", "scripts": { - "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/", - "lint": "yarn eslint 'src/**/*.js' 'test/**/*.js'", - "build": "yarn lint && yarn run-babel && yarn run-rollup && yarn run-uglify", + "clean": "rm -rf libcsm/ libesm/ dist/ coverage/ .nyc_output/", + "lint": "yarn eslint", + "build": "yarn lint && yarn generate-esm && yarn generate-cjs && yarn check-types && yarn run-rollup && yarn run-uglify", + "generate-cjs": "yarn tsc --module commonjs --outDir libcjs && echo '{\"type\": \"commonjs\"}' > libcjs/package.json", + "generate-esm": "yarn tsc --module nodenext --outDir libesm && echo '{\"type\": \"module\"}' > libesm/package.json", + "check-types": "yarn run-tsd && yarn run-attw", "test": "nyc yarn _test", "_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha", - "run-babel": "babel --out-dir lib --source-maps=inline src", + "run-attw": "yarn attw --pack --entrypoints . && yarn attw --pack --entrypoints lib/diff/word.js --profile node16", + "run-tsd": "yarn tsd --typings libesm/ && yarn tsd --files test-d/", "run-rollup": "rollup -c rollup.config.mjs", "run-uglify": "uglifyjs dist/diff.js -c -o dist/diff.min.js", "run-mocha": "mocha --require ./runtime 'test/**/*.js'" }, "devDependencies": { - "@babel/cli": "^7.26.4", + "@arethetypeswrong/cli": "^0.17.4", "@babel/core": "^7.26.9", - "@babel/eslint-parser": "^7.26.10", "@babel/preset-env": "^7.26.9", "@babel/register": "^7.25.9", "@colors/colors": "^1.6.0", + "@eslint/js": "^9.24.0", "babel-loader": "^9.2.1", "babel-plugin-istanbul": "^7.0.0", "chai": "^4.2.0", "cross-env": "^7.0.3", - "eslint": "^9.22.0", + "eslint": "^9.24.0", "globals": "^16.0.0", "karma": "^6.4.4", "karma-mocha": "^2.0.1", @@ -73,7 +103,9 @@ "mocha": "^11.1.0", "nyc": "^17.1.0", "rollup": "^4.34.8", - "rollup-plugin-babel": "^4.4.0", + "tsd": "^0.32.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.29.1", "uglify-js": "^3.19.3", "webpack": "^5.98.0", "webpack-dev-server": "^5.2.0" diff --git a/release-notes.md b/release-notes.md index 49d335a57..6856aae1d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -10,6 +10,18 @@ - [#581](https://github.com/kpdecker/jsdiff/pull/581) - **fixed some regex operations used for tokenization in `diffWords` taking O(n^2) time** in pathological cases - [#595](https://github.com/kpdecker/jsdiff/pull/595) - **fixed a crash in patch creation functions when handling a single hunk consisting of a very large number (e.g. >130k) of lines**. (This was caused by spreading indefinitely-large arrays to `.push()` using `.apply` or the spread operator and hitting the JS-implementation-specific limit on the maximum number of arguments to a function, as shown at https://stackoverflow.com/a/56809779/1709587; thus the exact threshold to hit the error will depend on the environment in which you were running JsDiff.) - [#596](https://github.com/kpdecker/jsdiff/pull/596) - **removed the `merge` function**. Previously JsDiff included an undocumented function called `merge` that was meant to, in some sense, merge patches. It had at least a couple of serious bugs that could lead to it returning unambiguously wrong results, and it was difficult to simply "fix" because it was [unclear precisely what it was meant to do](https://github.com/kpdecker/jsdiff/issues/181#issuecomment-2198319542). For now, the fix is to remove it entirely. +- [#591](https://github.com/kpdecker/jsdiff/pull/591) - JsDiff's source code has been rewritten in TypeScript. This change entails the following changes for end users: + * **the `diff` package on npm now includes its own TypeScript type definitions**. Users who previously used the `@types/diff` npm package from DefinitelyTyped should remove that dependency when upgrading JsDiff to v8. + + Note that the transition from the DefinitelyTyped types to JsDiff's own type definitions includes multiple fixes and also removes many exported types previously used for `options` arguments to diffing and patch-generation functions. (There are now different exported options types for abortable calls - ones with a `timeout` or `maxEditLength` that may give a result of `undefined` - and non-abortable calls.) See the TypeScript section of the README for some usage tips. + + * **The `Diff` object is now a class**. Custom extensions of `Diff`, as described in the "Defining custom diffing behaviors" section of the README, can therefore now be done by writing a `class CustomDiff extends Diff` and overriding methods, instead of the old way based on prototype inheritance. (I *think* code that did things the old way should still work, though!) + + * **`diff/lib/index.es6.js` and `diff/lib/index.mjs` no longer exist, and the ESM version of the library is no longer bundled into a single file.** + + * **The `ignoreWhitespace` option for `diffWords` is no longer included in the type declarations**. The effect of passing `ignoreWhitespace: true` has always been to make `diffWords` just call `diffWordsWithSpace` instead, which was confusing, because that behaviour doesn't seem properly described as "ignoring" whitespace at all. The property remains available to non-TypeScript applications for the sake of backwards compatability, but TypeScript applications will now see a type error if they try to pass `ignoreWhitespace: true` to `diffWords` and should change their code to call `diffWordsWithSpace` instead. + + * JsDiff no longer purports to support ES3 environments. (I'm pretty sure it never truly did, despite claiming to in its README, since even the 1.0.0 release used `Array.map` which was added in ES5.) - [#601](https://github.com/kpdecker/jsdiff/pull/601) - **`diffJson`'s `stringifyReplacer` option behaves more like `JSON.stringify`'s `replacer` argument now.** In particular: * Each key/value pair now gets passed through the replacer once instead of twice * The `key` passed to the replacer when the top-level object is passed in as `value` is now `""` (previously, was `undefined`), and the `key` passed with an array element is the array index as a string, like `"0"` or `"1"` (previously was whatever the key for the entire array was). Both the new behaviours match that of `JSON.stringify`. diff --git a/rollup.config.mjs b/rollup.config.mjs index 618cddc0a..dbd0ae50d 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,29 +1,15 @@ -import babel from 'rollup-plugin-babel'; import pkg from './package.json' with { type: 'json' }; export default [ // browser-friendly UMD build { - input: 'src/index.js', + input: 'libesm/index.js', output: [ { name: 'Diff', format: 'umd', - file: pkg.browser - }, - { - format: 'esm', - file: pkg.module - }, { - format: 'esm', - file: pkg.exports['.']['import'] + file: "./dist/diff.js" } - ], - plugins: [ - babel({ - babelrc: false, - presets: [['@babel/preset-env', { targets: {ie: '11'}, modules: false }]] - }) ] } ]; diff --git a/runtime.js b/runtime.js index 82ea7e696..e90041a39 100644 --- a/runtime.js +++ b/runtime.js @@ -1,3 +1,3 @@ require('@babel/register')({ - ignore: ['lib', 'node_modules'] + ignore: ['libcjs', 'libesm', 'node_modules'] }); diff --git a/src/convert/dmp.js b/src/convert/dmp.js deleted file mode 100644 index b411dc2de..000000000 --- a/src/convert/dmp.js +++ /dev/null @@ -1,19 +0,0 @@ -// See: http://code.google.com/p/google-diff-match-patch/wiki/API -export function convertChangesToDMP(changes) { - let ret = [], - change, - operation; - for (let i = 0; i < changes.length; i++) { - change = changes[i]; - if (change.added) { - operation = 1; - } else if (change.removed) { - operation = -1; - } else { - operation = 0; - } - - ret.push([operation, change.value]); - } - return ret; -} diff --git a/src/convert/dmp.ts b/src/convert/dmp.ts new file mode 100644 index 000000000..4a980a976 --- /dev/null +++ b/src/convert/dmp.ts @@ -0,0 +1,25 @@ +import type {ChangeObject} from '../types.js'; + +type DmpOperation = 1 | 0 | -1; + +/** + * converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library + */ +export function convertChangesToDMP(changes: ChangeObject[]): [DmpOperation, ValueT][] { + const ret: [DmpOperation, ValueT][] = []; + let change, + operation: DmpOperation; + for (let i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; +} diff --git a/src/convert/xml.js b/src/convert/xml.ts similarity index 63% rename from src/convert/xml.js rename to src/convert/xml.ts index 34aa46366..8f834c1ce 100644 --- a/src/convert/xml.js +++ b/src/convert/xml.ts @@ -1,7 +1,12 @@ -export function convertChangesToXML(changes) { - let ret = []; +import type {ChangeObject} from '../types.js'; + +/** + * converts a list of change objects to a serialized XML format + */ +export function convertChangesToXML(changes: ChangeObject[]): string { + const ret = []; for (let i = 0; i < changes.length; i++) { - let change = changes[i]; + const change = changes[i]; if (change.added) { ret.push(''); } else if (change.removed) { @@ -19,7 +24,7 @@ export function convertChangesToXML(changes) { return ret.join(''); } -function escapeHTML(s) { +function escapeHTML(s: string): string { let n = s; n = n.replace(/&/g, '&'); n = n.replace(/ extends Diff> { + tokenize(value: Array) { + return value.slice(); + } + + join(value: Array) { + return value; + } + + removeEmpty(value: Array) { + return value; + } +} + +export const arrayDiff = new ArrayDiff(); + +/** + * diffs two arrays of tokens, comparing each item for strict equality (===). + * @returns a list of change objects. + */ +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffCallbackNonabortable +): undefined; +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options: DiffArraysOptionsAbortable +): ChangeObject[] | undefined +export function diffArrays( + oldArr: T[], + newArr: T[], + options?: DiffArraysOptionsNonabortable +): ChangeObject[] +export function diffArrays( + oldArr: T[], + newArr: T[], + options?: any +): undefined | ChangeObject[] { + return arrayDiff.diff(oldArr, newArr, options); +} diff --git a/src/diff/base.js b/src/diff/base.js deleted file mode 100644 index e54ea5a76..000000000 --- a/src/diff/base.js +++ /dev/null @@ -1,264 +0,0 @@ -export default function Diff() {} - -Diff.prototype = { - diff(oldString, newString, options = {}) { - let callback = options.callback; - if (typeof options === 'function') { - callback = options; - options = {}; - } - - let self = this; - - function done(value) { - value = self.postProcess(value, options); - if (callback) { - setTimeout(function() { callback(value); }, 0); - return undefined; - } else { - return value; - } - } - - // Allow subclasses to massage the input prior to running - oldString = this.castInput(oldString, options); - newString = this.castInput(newString, options); - - oldString = this.removeEmpty(this.tokenize(oldString, options)); - newString = this.removeEmpty(this.tokenize(newString, options)); - - let newLen = newString.length, oldLen = oldString.length; - let editLength = 1; - let maxEditLength = newLen + oldLen; - if(options.maxEditLength != null) { - maxEditLength = Math.min(maxEditLength, options.maxEditLength); - } - const maxExecutionTime = options.timeout ?? Infinity; - const abortAfterTimestamp = Date.now() + maxExecutionTime; - - let bestPath = [{ oldPos: -1, lastComponent: undefined }]; - - // Seed editLength = 0, i.e. the content starts with the same values - let newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options); - if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { - // Identity per the equality and tokenizer - return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken)); - } - - // Once we hit the right edge of the edit graph on some diagonal k, we can - // definitely reach the end of the edit graph in no more than k edits, so - // there's no point in considering any moves to diagonal k+1 any more (from - // which we're guaranteed to need at least k+1 more edits). - // Similarly, once we've reached the bottom of the edit graph, there's no - // point considering moves to lower diagonals. - // We record this fact by setting minDiagonalToConsider and - // maxDiagonalToConsider to some finite value once we've hit the edge of - // the edit graph. - // This optimization is not faithful to the original algorithm presented in - // Myers's paper, which instead pointlessly extends D-paths off the end of - // the edit graph - see page 7 of Myers's paper which notes this point - // explicitly and illustrates it with a diagram. This has major performance - // implications for some common scenarios. For instance, to compute a diff - // where the new text simply appends d characters on the end of the - // original text of length n, the true Myers algorithm will take O(n+d^2) - // time while this optimization needs only O(n+d) time. - let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; - - // Main worker method. checks all permutations of a given edit length for acceptance. - function execEditLength() { - for ( - let diagonalPath = Math.max(minDiagonalToConsider, -editLength); - diagonalPath <= Math.min(maxDiagonalToConsider, editLength); - diagonalPath += 2 - ) { - let basePath; - let removePath = bestPath[diagonalPath - 1], - addPath = bestPath[diagonalPath + 1]; - if (removePath) { - // No one else is going to attempt to use this value, clear it - bestPath[diagonalPath - 1] = undefined; - } - - let canAdd = false; - if (addPath) { - // what newPos will be after we do an insertion: - const addPathNewPos = addPath.oldPos - diagonalPath; - canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; - } - - let canRemove = removePath && removePath.oldPos + 1 < oldLen; - if (!canAdd && !canRemove) { - // If this path is a terminal then prune - bestPath[diagonalPath] = undefined; - continue; - } - - // Select the diagonal that we want to branch from. We select the prior - // path whose position in the old string is the farthest from the origin - // and does not pass the bounds of the diff graph - if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) { - basePath = self.addToPath(addPath, true, false, 0, options); - } else { - basePath = self.addToPath(removePath, false, true, 1, options); - } - - newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options); - - if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { - // If we have hit the end of both strings, then we are done - return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken)) || true; - } else { - bestPath[diagonalPath] = basePath; - if (basePath.oldPos + 1 >= oldLen) { - maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); - } - if (newPos + 1 >= newLen) { - minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); - } - } - } - - editLength++; - } - - // Performs the length of edit iteration. Is a bit fugly as this has to support the - // sync and async mode which is never fun. Loops over execEditLength until a value - // is produced, or until the edit length exceeds options.maxEditLength (if given), - // in which case it will return undefined. - if (callback) { - (function exec() { - setTimeout(function() { - if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { - return callback(); - } - - if (!execEditLength()) { - exec(); - } - }, 0); - }()); - } else { - while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { - let ret = execEditLength(); - if (ret) { - return ret; - } - } - } - }, - - addToPath(path, added, removed, oldPosInc, options) { - let last = path.lastComponent; - if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { - return { - oldPos: path.oldPos + oldPosInc, - lastComponent: {count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent } - }; - } else { - return { - oldPos: path.oldPos + oldPosInc, - lastComponent: {count: 1, added: added, removed: removed, previousComponent: last } - }; - } - }, - extractCommon(basePath, newString, oldString, diagonalPath, options) { - let newLen = newString.length, - oldLen = oldString.length, - oldPos = basePath.oldPos, - newPos = oldPos - diagonalPath, - - commonCount = 0; - while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) { - newPos++; - oldPos++; - commonCount++; - if (options.oneChangePerToken) { - basePath.lastComponent = {count: 1, previousComponent: basePath.lastComponent, added: false, removed: false}; - } - } - - if (commonCount && !options.oneChangePerToken) { - basePath.lastComponent = {count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false}; - } - - basePath.oldPos = oldPos; - return newPos; - }, - - equals(left, right, options) { - if (options.comparator) { - return options.comparator(left, right); - } else { - return left === right - || (options.ignoreCase && left.toLowerCase() === right.toLowerCase()); - } - }, - removeEmpty(array) { - let ret = []; - for (let i = 0; i < array.length; i++) { - if (array[i]) { - ret.push(array[i]); - } - } - return ret; - }, - castInput(value) { - return value; - }, - tokenize(value) { - return Array.from(value); - }, - join(chars) { - return chars.join(''); - }, - postProcess(changeObjects) { - return changeObjects; - } -}; - -function buildValues(diff, lastComponent, newString, oldString, useLongestToken) { - // First we convert our linked list of components in reverse order to an - // array in the right order: - const components = []; - let nextComponent; - while (lastComponent) { - components.push(lastComponent); - nextComponent = lastComponent.previousComponent; - delete lastComponent.previousComponent; - lastComponent = nextComponent; - } - components.reverse(); - - let componentPos = 0, - componentLen = components.length, - newPos = 0, - oldPos = 0; - - for (; componentPos < componentLen; componentPos++) { - let component = components[componentPos]; - if (!component.removed) { - if (!component.added && useLongestToken) { - let value = newString.slice(newPos, newPos + component.count); - value = value.map(function(value, i) { - let oldValue = oldString[oldPos + i]; - return oldValue.length > value.length ? oldValue : value; - }); - - component.value = diff.join(value); - } else { - component.value = diff.join(newString.slice(newPos, newPos + component.count)); - } - newPos += component.count; - - // Common case - if (!component.added) { - oldPos += component.count; - } - } else { - component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); - oldPos += component.count; - } - } - - return components; -} diff --git a/src/diff/base.ts b/src/diff/base.ts new file mode 100644 index 000000000..cfc3719df --- /dev/null +++ b/src/diff/base.ts @@ -0,0 +1,365 @@ +import type {ChangeObject, AllDiffOptions, AbortableDiffOptions, DiffCallbackNonabortable, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackAbortable, TimeoutOption, MaxEditLengthOption} from '../types.js'; + +/** + * Like a ChangeObject, but with no value and an extra `previousComponent` property. + * A linked list of these (linked via `.previousComponent`) is used internally in the code below to + * keep track of the state of the diffing algorithm, but gets converted to an array of + * ChangeObjects before being returned to the caller. + */ +interface DraftChangeObject { + added: boolean; + removed: boolean; + count: number; + previousComponent?: DraftChangeObject; + + // Only added in buildValues: + value?: any; +} + +interface Path { + oldPos: number; + lastComponent: DraftChangeObject | undefined +} + +export default class Diff< + TokenT, + ValueT extends Iterable = Iterable +> { + diff( + oldStr: ValueT, + newStr: ValueT, + options: DiffCallbackNonabortable + ): undefined; + diff( + oldStr: ValueT, + newStr: ValueT, + options: AllDiffOptions & AbortableDiffOptions & CallbackOptionAbortable + ): undefined + diff( + oldStr: ValueT, + newStr: ValueT, + options: AllDiffOptions & CallbackOptionNonabortable + ): undefined + diff( + oldStr: ValueT, + newStr: ValueT, + options: AllDiffOptions & AbortableDiffOptions + ): ChangeObject[] | undefined + diff( + oldStr: ValueT, + newStr: ValueT, + options?: AllDiffOptions + ): ChangeObject[] + diff( + oldString: ValueT, + newString: ValueT, + // Type below is not accurate/complete - see above for full possibilities - but it compiles + options: DiffCallbackNonabortable | AllDiffOptions & Partial> = {} + ): ChangeObject[] | undefined { + let callback: DiffCallbackAbortable | DiffCallbackNonabortable | undefined; + if (typeof options === 'function') { + callback = options; + options = {}; + } else if ('callback' in options) { + callback = options.callback; + } + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString, options); + newString = this.castInput(newString, options); + + const oldTokens = this.removeEmpty(this.tokenize(oldString, options)); + const newTokens = this.removeEmpty(this.tokenize(newString, options)); + + return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); + } + + private diffWithOptionsObj( + oldTokens: TokenT[], + newTokens: TokenT[], + options: AllDiffOptions & Partial & Partial, + callback: DiffCallbackAbortable | DiffCallbackNonabortable | undefined + ): ChangeObject[] | undefined { + const done = (value: ChangeObject[]) => { + value = this.postProcess(value, options); + if (callback) { + setTimeout(function() { callback(value); }, 0); + return undefined; + } else { + return value; + } + }; + + const newLen = newTokens.length, oldLen = oldTokens.length; + let editLength = 1; + let maxEditLength = newLen + oldLen; + if(options.maxEditLength != null) { + maxEditLength = Math.min(maxEditLength, options.maxEditLength); + } + const maxExecutionTime = options.timeout ?? Infinity; + const abortAfterTimestamp = Date.now() + maxExecutionTime; + + const bestPath: Path[] = [{ oldPos: -1, lastComponent: undefined }]; + + // Seed editLength = 0, i.e. the content starts with the same values + let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); + if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // Identity per the equality and tokenizer + return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens)); + } + + // Once we hit the right edge of the edit graph on some diagonal k, we can + // definitely reach the end of the edit graph in no more than k edits, so + // there's no point in considering any moves to diagonal k+1 any more (from + // which we're guaranteed to need at least k+1 more edits). + // Similarly, once we've reached the bottom of the edit graph, there's no + // point considering moves to lower diagonals. + // We record this fact by setting minDiagonalToConsider and + // maxDiagonalToConsider to some finite value once we've hit the edge of + // the edit graph. + // This optimization is not faithful to the original algorithm presented in + // Myers's paper, which instead pointlessly extends D-paths off the end of + // the edit graph - see page 7 of Myers's paper which notes this point + // explicitly and illustrates it with a diagram. This has major performance + // implications for some common scenarios. For instance, to compute a diff + // where the new text simply appends d characters on the end of the + // original text of length n, the true Myers algorithm will take O(n+d^2) + // time while this optimization needs only O(n+d) time. + let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; + + // Main worker method. checks all permutations of a given edit length for acceptance. + const execEditLength = () => { + for ( + let diagonalPath = Math.max(minDiagonalToConsider, -editLength); + diagonalPath <= Math.min(maxDiagonalToConsider, editLength); + diagonalPath += 2 + ) { + let basePath; + const removePath = bestPath[diagonalPath - 1], + addPath = bestPath[diagonalPath + 1]; + if (removePath) { + // No one else is going to attempt to use this value, clear it + // @ts-expect-error - perf optimisation. This type-violating value will never be read. + bestPath[diagonalPath - 1] = undefined; + } + + let canAdd = false; + if (addPath) { + // what newPos will be after we do an insertion: + const addPathNewPos = addPath.oldPos - diagonalPath; + canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; + } + + const canRemove = removePath && removePath.oldPos + 1 < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + // @ts-expect-error - perf optimisation. This type-violating value will never be read. + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the old string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) { + basePath = this.addToPath(addPath, true, false, 0, options); + } else { + basePath = this.addToPath(removePath, false, true, 1, options); + } + + newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options); + + if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // If we have hit the end of both strings, then we are done + return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true; + } else { + bestPath[diagonalPath] = basePath; + if (basePath.oldPos + 1 >= oldLen) { + maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); + } + if (newPos + 1 >= newLen) { + minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); + } + } + } + + editLength++; + }; + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced, or until the edit length exceeds options.maxEditLength (if given), + // in which case it will return undefined. + if (callback) { + (function exec() { + setTimeout(function() { + if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { + return (callback as DiffCallbackAbortable)(undefined); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + }()); + } else { + while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { + const ret = execEditLength(); + if (ret) { + return ret as ChangeObject[]; + } + } + } + } + + private addToPath( + path: Path, + added: boolean, + removed: boolean, + oldPosInc: number, + options: AllDiffOptions + ): Path { + const last = path.lastComponent; + if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: {count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent } + }; + } else { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: {count: 1, added: added, removed: removed, previousComponent: last } + }; + } + } + + private extractCommon( + basePath: Path, + newTokens: TokenT[], + oldTokens: TokenT[], + diagonalPath: number, + options: AllDiffOptions + ): number { + const newLen = newTokens.length, + oldLen = oldTokens.length; + let oldPos = basePath.oldPos, + newPos = oldPos - diagonalPath, + commonCount = 0; + + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) { + newPos++; + oldPos++; + commonCount++; + if (options.oneChangePerToken) { + basePath.lastComponent = {count: 1, previousComponent: basePath.lastComponent, added: false, removed: false}; + } + } + + if (commonCount && !options.oneChangePerToken) { + basePath.lastComponent = {count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false}; + } + + basePath.oldPos = oldPos; + return newPos; + } + + equals(left: TokenT, right: TokenT, options: AllDiffOptions): boolean { + if (options.comparator) { + return options.comparator(left, right); + } else { + return left === right + || (!!options.ignoreCase && (left as string).toLowerCase() === (right as string).toLowerCase()); + } + } + + removeEmpty(array: TokenT[]): TokenT[] { + const ret: TokenT[] = []; + for (let i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + castInput(value: ValueT, options: AllDiffOptions): ValueT { + return value; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + tokenize(value: ValueT, options: AllDiffOptions): TokenT[] { + return Array.from(value); + } + + join(chars: TokenT[]): ValueT { + // Assumes ValueT is string, which is the case for most subclasses. + // When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op) + // Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF + // assume tokens and values are strings, but not completely - is weird and janky. + return (chars as string[]).join('') as unknown as ValueT; + } + + postProcess( + changeObjects: ChangeObject[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options: AllDiffOptions + ): ChangeObject[] { + return changeObjects; + } + + get useLongestToken(): boolean { + return false; + } + + private buildValues( + lastComponent: DraftChangeObject | undefined, + newTokens: TokenT[], + oldTokens: TokenT[] + ): ChangeObject[] { + // First we convert our linked list of components in reverse order to an + // array in the right order: + const components: DraftChangeObject[] = []; + let nextComponent; + while (lastComponent) { + components.push(lastComponent); + nextComponent = lastComponent.previousComponent; + delete lastComponent.previousComponent; + lastComponent = nextComponent; + } + components.reverse(); + + const componentLen = components.length; + let componentPos = 0, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + const component = components[componentPos]; + if (!component.removed) { + if (!component.added && this.useLongestToken) { + let value = newTokens.slice(newPos, newPos + component.count); + value = value.map(function(value, i) { + const oldValue = oldTokens[oldPos + i]; + return (oldValue as string).length > (value as string).length ? oldValue : value; + }); + + component.value = this.join(value); + } else { + component.value = this.join(newTokens.slice(newPos, newPos + component.count)); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + } + } + + return components as ChangeObject[]; + } +} + diff --git a/src/diff/character.js b/src/diff/character.js deleted file mode 100644 index e9f17b111..000000000 --- a/src/diff/character.js +++ /dev/null @@ -1,4 +0,0 @@ -import Diff from './base'; - -export const characterDiff = new Diff(); -export function diffChars(oldStr, newStr, options) { return characterDiff.diff(oldStr, newStr, options); } diff --git a/src/diff/character.ts b/src/diff/character.ts new file mode 100644 index 000000000..269624840 --- /dev/null +++ b/src/diff/character.ts @@ -0,0 +1,46 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffCharsOptionsAbortable, DiffCharsOptionsNonabortable} from '../types.js'; + +class CharacterDiff extends Diff {} + +export const characterDiff = new CharacterDiff(); + +/** + * diffs two blocks of text, treating each character as a token. + * + * ("Characters" here means Unicode code points - the elements you get when you loop over a string with a `for ... of ...` loop.) + * + * @returns a list of change objects. + */ +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffChars( + oldStr: string, + newStr: string, + options: DiffCharsOptionsAbortable +): ChangeObject[] | undefined +export function diffChars( + oldStr: string, + newStr: string, + options?: DiffCharsOptionsNonabortable +): ChangeObject[] +export function diffChars( + oldStr: string, + newStr: string, + options?: any +): undefined | ChangeObject[] { + return characterDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/css.js b/src/diff/css.js deleted file mode 100644 index e2e445a60..000000000 --- a/src/diff/css.js +++ /dev/null @@ -1,8 +0,0 @@ -import Diff from './base'; - -export const cssDiff = new Diff(); -cssDiff.tokenize = function(value) { - return value.split(/([{}:;,]|\s+)/); -}; - -export function diffCss(oldStr, newStr, callback) { return cssDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/css.ts b/src/diff/css.ts new file mode 100644 index 000000000..42080ba6d --- /dev/null +++ b/src/diff/css.ts @@ -0,0 +1,44 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffCssOptionsAbortable, DiffCssOptionsNonabortable} from '../types.js'; + +class CssDiff extends Diff { + tokenize(value: string) { + return value.split(/([{}:;,]|\s+)/); + } +} + +export const cssDiff = new CssDiff(); + +/** + * diffs two blocks of text, comparing CSS tokens. + * + * @returns a list of change objects. + */ +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffCss( + oldStr: string, + newStr: string, + options: DiffCssOptionsAbortable +): ChangeObject[] | undefined +export function diffCss( + oldStr: string, + newStr: string, + options?: DiffCssOptionsNonabortable +): ChangeObject[] +export function diffCss(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return cssDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/json.js b/src/diff/json.js deleted file mode 100644 index 94d29e8e8..000000000 --- a/src/diff/json.js +++ /dev/null @@ -1,80 +0,0 @@ -import Diff from './base'; -import {lineDiff} from './line'; - -export const jsonDiff = new Diff(); -// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a -// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: -jsonDiff.useLongestToken = true; - -jsonDiff.tokenize = lineDiff.tokenize; -jsonDiff.castInput = function(value, options) { - const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; - - return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' '); -}; -jsonDiff.equals = function(left, right, options) { - return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); -}; - -export function diffJson(oldObj, newObj, options) { return jsonDiff.diff(oldObj, newObj, options); } - -// This function handles the presence of circular references by bailing out when encountering an -// object that is already on the "stack" of items being processed. Accepts an optional replacer -export function canonicalize(obj, stack, replacementStack, replacer, key) { - stack = stack || []; - replacementStack = replacementStack || []; - - if (replacer) { - obj = replacer(key === undefined ? '' : key, obj); - } - - let i; - - for (i = 0; i < stack.length; i += 1) { - if (stack[i] === obj) { - return replacementStack[i]; - } - } - - let canonicalizedObj; - - if ('[object Array]' === Object.prototype.toString.call(obj)) { - stack.push(obj); - canonicalizedObj = new Array(obj.length); - replacementStack.push(canonicalizedObj); - for (i = 0; i < obj.length; i += 1) { - canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i)); - } - stack.pop(); - replacementStack.pop(); - return canonicalizedObj; - } - - if (obj && obj.toJSON) { - obj = obj.toJSON(); - } - - if (typeof obj === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - replacementStack.push(canonicalizedObj); - let sortedKeys = [], - key; - for (key in obj) { - /* istanbul ignore else */ - if (Object.prototype.hasOwnProperty.call(obj, key)) { - sortedKeys.push(key); - } - } - sortedKeys.sort(); - for (i = 0; i < sortedKeys.length; i += 1) { - key = sortedKeys[i]; - canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key); - } - stack.pop(); - replacementStack.pop(); - } else { - canonicalizedObj = obj; - } - return canonicalizedObj; -} diff --git a/src/diff/json.ts b/src/diff/json.ts new file mode 100644 index 000000000..be7e7c4cc --- /dev/null +++ b/src/diff/json.ts @@ -0,0 +1,127 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffJsonOptionsAbortable, DiffJsonOptionsNonabortable} from '../types.js'; +import { tokenize } from './line.js'; + +class JsonDiff extends Diff { + get useLongestToken() { + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + return true; + } + + tokenize = tokenize; + + castInput(value: string, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) { + const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = options; + + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' '); + } + + equals(left: string, right: string, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) { + return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); + } +} + +export const jsonDiff = new JsonDiff(); + +/** + * diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token. + * Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result. + * + * @returns a list of change objects. + */ +export function diffJson( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffJson( + oldStr: string, + newStr: string, + options: DiffJsonOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffJson( + oldStr: string, + newStr: string, + options: DiffJsonOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffJson( + oldStr: string, + newStr: string, + options: DiffJsonOptionsAbortable +): ChangeObject[] | undefined +export function diffJson( + oldStr: string, + newStr: string, + options?: DiffJsonOptionsNonabortable +): ChangeObject[] +export function diffJson(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return jsonDiff.diff(oldStr, newStr, options); +} + + +// This function handles the presence of circular references by bailing out when encountering an +// object that is already on the "stack" of items being processed. Accepts an optional replacer +export function canonicalize( + obj: any, + stack: Array | null, replacementStack: Array | null, + replacer: (k: string, v: any) => any, + key?: string +) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key === undefined ? '' : key, obj); + } + + let i; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + let canonicalizedObj: any; + + if ('[object Array]' === Object.prototype.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i)); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + const sortedKeys = []; + let key; + for (key in obj) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(obj, key)) { + sortedKeys.push(key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; +} diff --git a/src/diff/line.js b/src/diff/line.js deleted file mode 100644 index 5d313013f..000000000 --- a/src/diff/line.js +++ /dev/null @@ -1,70 +0,0 @@ -import Diff from './base'; -import {generateOptions} from '../util/params'; - -export const lineDiff = new Diff(); -lineDiff.tokenize = function(value, options) { - if(options.stripTrailingCr) { - // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior - value = value.replace(/\r\n/g, '\n'); - } - - let retLines = [], - linesAndNewlines = value.split(/(\n|\r\n)/); - - // Ignore the final empty token that occurs if the string ends with a new line - if (!linesAndNewlines[linesAndNewlines.length - 1]) { - linesAndNewlines.pop(); - } - - // Merge the content and line separators into single tokens - for (let i = 0; i < linesAndNewlines.length; i++) { - let line = linesAndNewlines[i]; - - if (i % 2 && !options.newlineIsToken) { - retLines[retLines.length - 1] += line; - } else { - retLines.push(line); - } - } - - return retLines; -}; - -lineDiff.equals = function(left, right, options) { - // If we're ignoring whitespace, we need to normalise lines by stripping - // whitespace before checking equality. (This has an annoying interaction - // with newlineIsToken that requires special handling: if newlines get their - // own token, then we DON'T want to trim the *newline* tokens down to empty - // strings, since this would cause us to treat whitespace-only line content - // as equal to a separator between lines, which would be weird and - // inconsistent with the documented behavior of the options.) - if (options.ignoreWhitespace) { - if (!options.newlineIsToken || !left.includes('\n')) { - left = left.trim(); - } - if (!options.newlineIsToken || !right.includes('\n')) { - right = right.trim(); - } - } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { - if (left.endsWith('\n')) { - left = left.slice(0, -1); - } - if (right.endsWith('\n')) { - right = right.slice(0, -1); - } - } - return Diff.prototype.equals.call(this, left, right, options); -}; - -export function diffLines(oldStr, newStr, callback) { return lineDiff.diff(oldStr, newStr, callback); } - -// Kept for backwards compatibility. This is a rather arbitrary wrapper method -// that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to -// have two ways to do exactly the same thing in the API, so we no longer -// document this one (library users should explicitly use `diffLines` with -// `ignoreWhitespace: true` instead) but we keep it around to maintain -// compatibility with code that used old versions. -export function diffTrimmedLines(oldStr, newStr, callback) { - let options = generateOptions(callback, {ignoreWhitespace: true}); - return lineDiff.diff(oldStr, newStr, options); -} diff --git a/src/diff/line.ts b/src/diff/line.ts new file mode 100644 index 000000000..53de7c21d --- /dev/null +++ b/src/diff/line.ts @@ -0,0 +1,133 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffLinesOptionsAbortable, DiffLinesOptionsNonabortable} from '../types.js'; +import {generateOptions} from '../util/params.js'; + +class LineDiff extends Diff { + tokenize = tokenize; + + equals(left: string, right: string, options: DiffLinesOptionsAbortable | DiffLinesOptionsNonabortable) { + // If we're ignoring whitespace, we need to normalise lines by stripping + // whitespace before checking equality. (This has an annoying interaction + // with newlineIsToken that requires special handling: if newlines get their + // own token, then we DON'T want to trim the *newline* tokens down to empty + // strings, since this would cause us to treat whitespace-only line content + // as equal to a separator between lines, which would be weird and + // inconsistent with the documented behavior of the options.) + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes('\n')) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes('\n')) { + right = right.trim(); + } + } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { + if (left.endsWith('\n')) { + left = left.slice(0, -1); + } + if (right.endsWith('\n')) { + right = right.slice(0, -1); + } + } + return super.equals(left, right, options); + } +} + +export const lineDiff = new LineDiff(); + +/** + * diffs two blocks of text, treating each line as a token. + * @returns a list of change objects. + */ +export function diffLines( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable +): ChangeObject[] | undefined +export function diffLines( + oldStr: string, + newStr: string, + options?: DiffLinesOptionsNonabortable +): ChangeObject[] +export function diffLines(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return lineDiff.diff(oldStr, newStr, options); +} + +// Kept for backwards compatibility. This is a rather arbitrary wrapper method +// that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to +// have two ways to do exactly the same thing in the API, so we no longer +// document this one (library users should explicitly use `diffLines` with +// `ignoreWhitespace: true` instead) but we keep it around to maintain +// compatibility with code that used old versions. +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options: DiffLinesOptionsAbortable +): ChangeObject[] | undefined +export function diffTrimmedLines( + oldStr: string, + newStr: string, + options?: DiffLinesOptionsNonabortable +): ChangeObject[] +export function diffTrimmedLines(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + options = generateOptions(options, {ignoreWhitespace: true}); + return lineDiff.diff(oldStr, newStr, options); +} + +// Exported standalone so it can be used from jsonDiff too. +export function tokenize(value: string, options: DiffLinesOptionsAbortable | DiffLinesOptionsNonabortable) { + if(options.stripTrailingCr) { + // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior + value = value.replace(/\r\n/g, '\n'); + } + + const retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (let i = 0; i < linesAndNewlines.length; i++) { + const line = linesAndNewlines[i]; + + if (i % 2 && !options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + retLines.push(line); + } + } + + return retLines; +} diff --git a/src/diff/sentence.js b/src/diff/sentence.js deleted file mode 100644 index fe699b6dd..000000000 --- a/src/diff/sentence.js +++ /dev/null @@ -1,9 +0,0 @@ -import Diff from './base'; - - -export const sentenceDiff = new Diff(); -sentenceDiff.tokenize = function(value) { - return value.split(/(?<=[.!?])(\s+|$)/); -}; - -export function diffSentences(oldStr, newStr, callback) { return sentenceDiff.diff(oldStr, newStr, callback); } diff --git a/src/diff/sentence.ts b/src/diff/sentence.ts new file mode 100644 index 000000000..098711ef7 --- /dev/null +++ b/src/diff/sentence.ts @@ -0,0 +1,47 @@ +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffSentencesOptionsAbortable, DiffSentencesOptionsNonabortable} from '../types.js'; + +class SentenceDiff extends Diff { + tokenize(value: string) { + return value.split(/(?<=[.!?])(\s+|$)/); + } +} + +export const sentenceDiff = new SentenceDiff(); + +/** + * diffs two blocks of text, treating each sentence, and the whitespace between each pair of sentences, as a token. + * The characters `.`, `!`, and `?`, when followed by whitespace, are treated as marking the end of a sentence; nothing else besides the end of the string is considered to mark a sentence end. + * + * (For more sophisticated detection of sentence breaks, including support for non-English punctuation, consider instead tokenizing with an [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) with `granularity: 'sentence'` and passing the result to `diffArrays`.) + * + * @returns a list of change objects. + */ +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffSentences( + oldStr: string, + newStr: string, + options: DiffSentencesOptionsAbortable +): ChangeObject[] | undefined +export function diffSentences( + oldStr: string, + newStr: string, + options?: DiffSentencesOptionsNonabortable +): ChangeObject[] +export function diffSentences(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return sentenceDiff.diff(oldStr, newStr, options); +} diff --git a/src/diff/word.js b/src/diff/word.ts similarity index 56% rename from src/diff/word.js rename to src/diff/word.ts index 3bd534ed4..a7239c25e 100644 --- a/src/diff/word.js +++ b/src/diff/word.ts @@ -1,5 +1,6 @@ -import Diff from './base'; -import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string'; +import Diff from './base.js'; +import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffWordsOptionsAbortable, DiffWordsOptionsNonabortable} from '../types.js'; +import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs } from '../util/string.js'; // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode // @@ -48,96 +49,132 @@ const extendedWordChars = 'a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2 // tokens. const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug'); -export const wordDiff = new Diff(); -wordDiff.equals = function(left, right, options) { - if (options.ignoreCase) { - left = left.toLowerCase(); - right = right.toLowerCase(); - } - - return left.trim() === right.trim(); -}; -wordDiff.tokenize = function(value, options = {}) { - let parts; - if (options.intlSegmenter) { - if (options.intlSegmenter.resolvedOptions().granularity != 'word') { - throw new Error('The segmenter passed must have a granularity of "word"'); +class WordDiff extends Diff { + equals(left: string, right: string, options: DiffWordsOptionsAbortable | DiffWordsOptionsNonabortable) { + if (options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); } - parts = Array.from(options.intlSegmenter.segment(value), segment => segment.segment); - } else { - parts = value.match(tokenizeIncludingWhitespace) || []; + + return left.trim() === right.trim(); } - const tokens = []; - let prevPart = null; - parts.forEach(part => { - if ((/\s/).test(part)) { - if (prevPart == null) { - tokens.push(part); - } else { - tokens.push(tokens.pop() + part); - } - } else if ((/\s/).test(prevPart)) { - if (tokens[tokens.length - 1] == prevPart) { - tokens.push(tokens.pop() + part); - } else { - tokens.push(prevPart + part); + + tokenize(value: string, options: DiffWordsOptionsAbortable | DiffWordsOptionsNonabortable = {}) { + let parts; + if (options.intlSegmenter) { + const segmenter: Intl.Segmenter = options.intlSegmenter; + if (segmenter.resolvedOptions().granularity != 'word') { + throw new Error('The segmenter passed must have a granularity of "word"'); } + parts = Array.from(segmenter.segment(value), segment => segment.segment); } else { - tokens.push(part); + parts = value.match(tokenizeIncludingWhitespace) || []; } + const tokens: string[] = []; + let prevPart: string | null = null; + parts.forEach(part => { + if ((/\s/).test(part)) { + if (prevPart == null) { + tokens.push(part); + } else { + tokens.push(tokens.pop() + part); + } + } else if (prevPart != null && (/\s/).test(prevPart)) { + if (tokens[tokens.length - 1] == prevPart) { + tokens.push(tokens.pop() + part); + } else { + tokens.push(prevPart + part); + } + } else { + tokens.push(part); + } - prevPart = part; - }); - return tokens; -}; - -wordDiff.join = function(tokens) { - // Tokens being joined here will always have appeared consecutively in the - // same text, so we can simply strip off the leading whitespace from all the - // tokens except the first (and except any whitespace-only tokens - but such - // a token will always be the first and only token anyway) and then join them - // and the whitespace around words and punctuation will end up correct. - return tokens.map((token, i) => { - if (i == 0) { - return token; - } else { - return token.replace((/^\s+/), ''); - } - }).join(''); -}; + prevPart = part; + }); + return tokens; + } -wordDiff.postProcess = function(changes, options) { - if (!changes || options.oneChangePerToken) { - return changes; + join(tokens: string[]) { + // Tokens being joined here will always have appeared consecutively in the + // same text, so we can simply strip off the leading whitespace from all the + // tokens except the first (and except any whitespace-only tokens - but such + // a token will always be the first and only token anyway) and then join them + // and the whitespace around words and punctuation will end up correct. + return tokens.map((token, i) => { + if (i == 0) { + return token; + } else { + return token.replace((/^\s+/), ''); + } + }).join(''); } - let lastKeep = null; - // Change objects representing any insertion or deletion since the last - // "keep" change object. There can be at most one of each. - let insertion = null; - let deletion = null; - changes.forEach(change => { - if (change.added) { - insertion = change; - } else if (change.removed) { - deletion = change; - } else { - if (insertion || deletion) { // May be false at start of text - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + postProcess(changes: ChangeObject[], options: any) { + if (!changes || options.oneChangePerToken) { + return changes; + } + + let lastKeep: ChangeObject | null = null; + // Change objects representing any insertion or deletion since the last + // "keep" change object. There can be at most one of each. + let insertion: ChangeObject | null = null; + let deletion: ChangeObject | null = null; + changes.forEach(change => { + if (change.added) { + insertion = change; + } else if (change.removed) { + deletion = change; + } else { + if (insertion || deletion) { // May be false at start of text + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + } + lastKeep = change; + insertion = null; + deletion = null; } - lastKeep = change; - insertion = null; - deletion = null; + }); + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); } - }); - if (insertion || deletion) { - dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); + return changes; } - return changes; -}; +} -export function diffWords(oldStr, newStr, options) { +export const wordDiff = new WordDiff(); + +/** + * diffs two blocks of text, treating each word and each punctuation mark as a token. + * Whitespace is ignored when computing the diff (but preserved as far as possible in the final change objects). + * + * @returns a list of change objects. + */ +export function diffWords( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffWords( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable +): ChangeObject[] | undefined +export function diffWords( + oldStr: string, + newStr: string, + options?: DiffWordsOptionsNonabortable +): ChangeObject[] +export function diffWords(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { // This option has never been documented and never will be (it's clearer to // just call `diffWordsWithSpace` directly if you need that behavior), but // has existed in jsdiff for a long time, so we retain support for it here @@ -149,7 +186,12 @@ export function diffWords(oldStr, newStr, options) { return wordDiff.diff(oldStr, newStr, options); } -function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) { +function dedupeWhitespaceInChangeObjects( + startKeep: ChangeObject | null, + deletion: ChangeObject | null, + insertion: ChangeObject | null, + endKeep: ChangeObject | null +) { // Before returning, we tidy up the leading and trailing whitespace of the // change objects to eliminate cases where trailing whitespace in one object // is repeated as leading whitespace in the next. @@ -228,13 +270,13 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep // otherwise we've got a deletion and no insertion } else if (startKeep && endKeep) { const newWsFull = leadingWs(endKeep.value), - delWsStart = leadingWs(deletion.value), - delWsEnd = trailingWs(deletion.value); + delWsStart = leadingWs(deletion!.value), + delWsEnd = trailingWs(deletion!.value); // Any whitespace that comes straight after startKeep in both the old and // new texts, assign to startKeep and remove from the deletion. const newWsStart = longestCommonPrefix(newWsFull, delWsStart); - deletion.value = removePrefix(deletion.value, newWsStart); + deletion!.value = removePrefix(deletion!.value, newWsStart); // Any whitespace that comes straight before endKeep in both the old and // new texts, and hasn't already been assigned to startKeep, assign to @@ -243,7 +285,7 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep removePrefix(newWsFull, newWsStart), delWsEnd ); - deletion.value = removeSuffix(deletion.value, newWsEnd); + deletion!.value = removeSuffix(deletion!.value, newWsEnd); endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd); // If there's any whitespace from the new text that HASN'T already been @@ -258,31 +300,64 @@ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep // endKeep, and just remove whitespace from the end of deletion to the // extent that it overlaps with the start of endKeep. const endKeepWsPrefix = leadingWs(endKeep.value); - const deletionWsSuffix = trailingWs(deletion.value); + const deletionWsSuffix = trailingWs(deletion!.value); const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix); - deletion.value = removeSuffix(deletion.value, overlap); + deletion!.value = removeSuffix(deletion!.value, overlap); } else if (startKeep) { // We are at the END of the text. Preserve all the whitespace on // startKeep, and just remove whitespace from the start of deletion to // the extent that it overlaps with the end of startKeep. const startKeepWsSuffix = trailingWs(startKeep.value); - const deletionWsPrefix = leadingWs(deletion.value); + const deletionWsPrefix = leadingWs(deletion!.value); const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix); - deletion.value = removePrefix(deletion.value, overlap); + deletion!.value = removePrefix(deletion!.value, overlap); + } +} + + +class WordsWithSpaceDiff extends Diff { + tokenize(value: string) { + // Slightly different to the tokenizeIncludingWhitespace regex used above in + // that this one treats each individual newline as a distinct tokens, rather + // than merging them into other surrounding whitespace. This was requested + // in https://github.com/kpdecker/jsdiff/issues/180 & + // https://github.com/kpdecker/jsdiff/issues/211 + const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug'); + return value.match(regex) || []; } } +export const wordsWithSpaceDiff = new WordsWithSpaceDiff(); -export const wordWithSpaceDiff = new Diff(); -wordWithSpaceDiff.tokenize = function(value) { - // Slightly different to the tokenizeIncludingWhitespace regex used above in - // that this one treats each individual newline as a distinct tokens, rather - // than merging them into other surrounding whitespace. This was requested - // in https://github.com/kpdecker/jsdiff/issues/180 & - // https://github.com/kpdecker/jsdiff/issues/211 - const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug'); - return value.match(regex) || []; -}; -export function diffWordsWithSpace(oldStr, newStr, options) { - return wordWithSpaceDiff.diff(oldStr, newStr, options); +/** + * diffs two blocks of text, treating each word, punctuation mark, newline, or run of (non-newline) whitespace as a token. + * @returns a list of change objects + */ +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffCallbackNonabortable +): undefined; +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable & CallbackOptionAbortable +): undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsNonabortable & CallbackOptionNonabortable +): undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options: DiffWordsOptionsAbortable +): ChangeObject[] | undefined +export function diffWordsWithSpace( + oldStr: string, + newStr: string, + options?: DiffWordsOptionsNonabortable +): ChangeObject[] +export function diffWordsWithSpace(oldStr: string, newStr: string, options?: any): undefined | ChangeObject[] { + return wordsWithSpaceDiff.diff(oldStr, newStr, options); } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index aa679ce61..000000000 --- a/src/index.js +++ /dev/null @@ -1,62 +0,0 @@ -/* See LICENSE file for terms of use */ - -/* - * Text diff implementation. - * - * This library supports the following APIs: - * Diff.diffChars: Character by character diff - * Diff.diffWords: Word (as defined by \b regex) diff which ignores whitespace - * Diff.diffLines: Line based diff - * - * Diff.diffCss: Diff targeted at CSS content - * - * These methods are based on the implementation proposed in - * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). - * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 - */ -import Diff from './diff/base'; -import {diffChars} from './diff/character'; -import {diffWords, diffWordsWithSpace} from './diff/word'; -import {diffLines, diffTrimmedLines} from './diff/line'; -import {diffSentences} from './diff/sentence'; - -import {diffCss} from './diff/css'; -import {diffJson, canonicalize} from './diff/json'; - -import {diffArrays} from './diff/array'; - -import {applyPatch, applyPatches} from './patch/apply'; -import {parsePatch} from './patch/parse'; -import {reversePatch} from './patch/reverse'; -import {structuredPatch, createTwoFilesPatch, createPatch, formatPatch} from './patch/create'; - -import {convertChangesToDMP} from './convert/dmp'; -import {convertChangesToXML} from './convert/xml'; - -export { - Diff, - - diffChars, - diffWords, - diffWordsWithSpace, - diffLines, - diffTrimmedLines, - diffSentences, - - diffCss, - diffJson, - - diffArrays, - - structuredPatch, - createTwoFilesPatch, - createPatch, - formatPatch, - applyPatch, - applyPatches, - parsePatch, - reversePatch, - convertChangesToDMP, - convertChangesToXML, - canonicalize -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..edec16ddc --- /dev/null +++ b/src/index.ts @@ -0,0 +1,129 @@ +/* See LICENSE file for terms of use */ + +/* + * Text diff implementation. + * + * This library supports the following APIs: + * Diff.diffChars: Character by character diff + * Diff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * Diff.diffLines: Line based diff + * + * Diff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +import Diff from './diff/base.js'; +import {diffChars, characterDiff} from './diff/character.js'; +import {diffWords, diffWordsWithSpace, wordDiff, wordsWithSpaceDiff} from './diff/word.js'; +import {diffLines, diffTrimmedLines, lineDiff} from './diff/line.js'; +import {diffSentences, sentenceDiff} from './diff/sentence.js'; + +import {diffCss, cssDiff} from './diff/css.js'; +import {diffJson, canonicalize, jsonDiff} from './diff/json.js'; + +import {diffArrays, arrayDiff} from './diff/array.js'; + +import {applyPatch, applyPatches} from './patch/apply.js'; +import type {ApplyPatchOptions, ApplyPatchesOptions} from './patch/apply.js'; +import {parsePatch} from './patch/parse.js'; +import {reversePatch} from './patch/reverse.js'; +import { + structuredPatch, + createTwoFilesPatch, + createPatch, + formatPatch +} from './patch/create.js'; +import type { + StructuredPatchOptionsAbortable, + StructuredPatchOptionsNonabortable, + CreatePatchOptionsAbortable, + CreatePatchOptionsNonabortable +} from './patch/create.js'; + +import {convertChangesToDMP} from './convert/dmp.js'; +import {convertChangesToXML} from './convert/xml.js'; +import type { + ChangeObject, + Change, + DiffArraysOptionsAbortable, + DiffArraysOptionsNonabortable, + DiffCharsOptionsAbortable, + DiffCharsOptionsNonabortable, + DiffLinesOptionsAbortable, + DiffLinesOptionsNonabortable, + DiffWordsOptionsAbortable, + DiffWordsOptionsNonabortable, + DiffSentencesOptionsAbortable, + DiffSentencesOptionsNonabortable, + DiffJsonOptionsAbortable, + DiffJsonOptionsNonabortable, + DiffCssOptionsAbortable, + DiffCssOptionsNonabortable, + StructuredPatch, + StructuredPatchHunk +} from './types.js'; + +export { + Diff, + + diffChars, + characterDiff, + diffWords, + wordDiff, + diffWordsWithSpace, + wordsWithSpaceDiff, + diffLines, + lineDiff, + diffTrimmedLines, + diffSentences, + sentenceDiff, + diffCss, + cssDiff, + diffJson, + jsonDiff, + diffArrays, + arrayDiff, + + structuredPatch, + createTwoFilesPatch, + createPatch, + formatPatch, + applyPatch, + applyPatches, + parsePatch, + reversePatch, + convertChangesToDMP, + convertChangesToXML, + canonicalize +}; + +export type { + ChangeObject, + Change, + DiffArraysOptionsAbortable, + DiffArraysOptionsNonabortable, + DiffCharsOptionsAbortable, + DiffCharsOptionsNonabortable, + DiffLinesOptionsAbortable, + DiffLinesOptionsNonabortable, + DiffWordsOptionsAbortable, + DiffWordsOptionsNonabortable, + DiffSentencesOptionsAbortable, + DiffSentencesOptionsNonabortable, + DiffJsonOptionsAbortable, + DiffJsonOptionsNonabortable, + DiffCssOptionsAbortable, + DiffCssOptionsNonabortable, + StructuredPatch, + StructuredPatchHunk, + + ApplyPatchOptions, + ApplyPatchesOptions, + + StructuredPatchOptionsAbortable, + StructuredPatchOptionsNonabortable, + CreatePatchOptionsAbortable, + CreatePatchOptionsNonabortable +}; diff --git a/src/patch/apply.js b/src/patch/apply.ts similarity index 53% rename from src/patch/apply.js rename to src/patch/apply.ts index 612e0658b..e4fd21874 100644 --- a/src/patch/apply.js +++ b/src/patch/apply.ts @@ -1,36 +1,96 @@ -import {hasOnlyWinLineEndings, hasOnlyUnixLineEndings} from '../util/string'; -import {isWin, isUnix, unixToWin, winToUnix} from './line-endings'; -import {parsePatch} from './parse'; -import distanceIterator from '../util/distance-iterator'; +import {hasOnlyWinLineEndings, hasOnlyUnixLineEndings} from '../util/string.js'; +import {isWin, isUnix, unixToWin, winToUnix} from './line-endings.js'; +import {parsePatch} from './parse.js'; +import distanceIterator from '../util/distance-iterator.js'; +import type { StructuredPatch } from '../types.js'; -export function applyPatch(source, uniDiff, options = {}) { - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } +export interface ApplyPatchOptions { + /** + * Maximum Levenshtein distance (in lines deleted, added, or subtituted) between the context shown in a patch hunk and the lines found in the file. + * @default 0 + */ + fuzzFactor?: number, + /** + * If `true`, and if the file to be patched consistently uses different line endings to the patch (i.e. either the file always uses Unix line endings while the patch uses Windows ones, or vice versa), then `applyPatch` will behave as if the line endings in the patch were the same as those in the source file. + * (If `false`, the patch will usually fail to apply in such circumstances since lines deleted in the patch won't be considered to match those in the source file.) + * @default true + */ + autoConvertLineEndings?: boolean, + /** + * Callback used to compare to given lines to determine if they should be considered equal when patching. + * Defaults to strict equality but may be overridden to provide fuzzier comparison. + * Should return false if the lines should be rejected. + */ + compareLine?: (lineNumber: number, line: string, operation: string, patchContent: string) => boolean, +} - if (Array.isArray(uniDiff)) { - if (uniDiff.length > 1) { - throw new Error('applyPatch only works with a single input.'); - } +interface ApplyHunkReturnType { + patchedLines: string[]; + oldLineLastI: number; +} + +/** + * attempts to apply a unified diff patch. + * + * Hunks are applied first to last. + * `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly. + * If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly. + * If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match. + * Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly. + * + * Once a hunk is successfully fitted, the process begins again with the next hunk. + * Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks. + * + * If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`. + * + * If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly. + * (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.) + * + * If the patch was applied successfully, returns a string containing the patched text. + * If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false. + * + * @param patch a string diff or the output from the `parsePatch` or `structuredPatch` methods. + */ +export function applyPatch( + source: string, + patch: string | StructuredPatch | [StructuredPatch], + options: ApplyPatchOptions = {} +): string | false { + let patches: StructuredPatch[]; + if (typeof patch === 'string') { + patches = parsePatch(patch); + } else if (Array.isArray(patch)) { + patches = patch; + } else { + patches = [patch]; + } - uniDiff = uniDiff[0]; + if (patches.length > 1) { + throw new Error('applyPatch only works with a single input.'); } + return applyStructuredPatch(source, patches[0], options); +} + +function applyStructuredPatch( + source: string, + patch: StructuredPatch, + options: ApplyPatchOptions = {} +): string | false { if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) { - if (hasOnlyWinLineEndings(source) && isUnix(uniDiff)) { - uniDiff = unixToWin(uniDiff); - } else if (hasOnlyUnixLineEndings(source) && isWin(uniDiff)) { - uniDiff = winToUnix(uniDiff); + if (hasOnlyWinLineEndings(source) && isUnix(patch)) { + patch = unixToWin(patch); + } else if (hasOnlyUnixLineEndings(source) && isWin(patch)) { + patch = winToUnix(patch); } } // Apply the diff to the input - let lines = source.split('\n'), - hunks = uniDiff.hunks, - - compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), - fuzzFactor = options.fuzzFactor || 0, - minLine = 0; + const lines = source.split('\n'), + hunks = patch.hunks, + compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), + fuzzFactor = options.fuzzFactor || 0; + let minLine = 0; if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) { throw new Error('fuzzFactor must be a non-negative integer'); @@ -94,18 +154,18 @@ export function applyPatch(source, uniDiff, options = {}) { * `replacementLines`. Otherwise, returns null. */ function applyHunk( - hunkLines, - toPos, - maxErrors, - hunkLinesI = 0, - lastContextLineMatched = true, - patchedLines = [], - patchedLinesLength = 0 - ) { + hunkLines: string[], + toPos: number, + maxErrors: number, + hunkLinesI: number = 0, + lastContextLineMatched: boolean = true, + patchedLines: string[] = [], + patchedLinesLength: number = 0 + ): ApplyHunkReturnType | null { let nConsecutiveOldContextLines = 0; let nextContextLineMustMatch = false; for (; hunkLinesI < hunkLines.length; hunkLinesI++) { - let hunkLine = hunkLines[hunkLinesI], + const hunkLine = hunkLines[hunkLinesI], operation = (hunkLine.length > 0 ? hunkLine[0] : ' '), content = (hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine); @@ -204,18 +264,18 @@ export function applyPatch(source, uniDiff, options = {}) { }; } - const resultLines = []; + const resultLines: string[] = []; // Search best fit offsets for each hunk based on the previous ones let prevHunkOffset = 0; for (let i = 0; i < hunks.length; i++) { const hunk = hunks[i]; let hunkResult; - let maxLine = lines.length - hunk.oldLines + fuzzFactor; - let toPos; + const maxLine = lines.length - hunk.oldLines + fuzzFactor; + let toPos: number | undefined; for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) { toPos = hunk.oldStart + prevHunkOffset - 1; - let iterator = distanceIterator(toPos, minLine, maxLine); + const iterator = distanceIterator(toPos, minLine, maxLine); for (; toPos !== undefined; toPos = iterator()) { hunkResult = applyHunk(hunk.lines, toPos, maxErrors); if (hunkResult) { @@ -232,7 +292,7 @@ export function applyPatch(source, uniDiff, options = {}) { } // Copy everything from the end of where we applied the last hunk to the start of this hunk - for (let i = minLine; i < toPos; i++) { + for (let i = minLine; i < toPos!; i++) { resultLines.push(lines[i]); } @@ -248,7 +308,7 @@ export function applyPatch(source, uniDiff, options = {}) { // Note the offset between where the patch said the hunk should've applied and where we // applied it, so we can adjust future hunks accordingly: - prevHunkOffset = toPos + 1 - hunk.oldStart; + prevHunkOffset = toPos! + 1 - hunk.oldStart; } // Copy over the rest of the lines from the old text @@ -259,26 +319,41 @@ export function applyPatch(source, uniDiff, options = {}) { return resultLines.join('\n'); } -// Wrapper that supports multiple file patches via callbacks. -export function applyPatches(uniDiff, options) { - if (typeof uniDiff === 'string') { - uniDiff = parsePatch(uniDiff); - } +export interface ApplyPatchesOptions extends ApplyPatchOptions { + loadFile: (index: StructuredPatch, callback: (err: any, data: string) => void) => void, + patched: (index: StructuredPatch, content: string | false, callback: (err: any) => void) => void, + complete: (err?: any) => void, +} + +/** + * applies one or more patches. + * + * `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files). + * + * This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is: + * + * - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution. + * - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution. + * + * Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made. + */ +export function applyPatches(uniDiff: string | StructuredPatch[], options: ApplyPatchesOptions): void { + const spDiff: StructuredPatch[] = typeof uniDiff === 'string' ? parsePatch(uniDiff) : uniDiff; let currentIndex = 0; - function processIndex() { - let index = uniDiff[currentIndex++]; + function processIndex(): void { + const index = spDiff[currentIndex++]; if (!index) { return options.complete(); } - options.loadFile(index, function(err, data) { + options.loadFile(index, function(err: any, data: string) { if (err) { return options.complete(err); } - let updatedContent = applyPatch(data, index, options); - options.patched(index, updatedContent, function(err) { + const updatedContent = applyPatch(data, index, options); + options.patched(index, updatedContent, function(err: any) { if (err) { return options.complete(err); } diff --git a/src/patch/create.js b/src/patch/create.js deleted file mode 100644 index f73f6379a..000000000 --- a/src/patch/create.js +++ /dev/null @@ -1,224 +0,0 @@ -import {diffLines} from '../diff/line'; - -export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - if (!options) { - options = {}; - } - if (typeof options === 'function') { - options = {callback: options}; - } - if (typeof options.context === 'undefined') { - options.context = 4; - } - if (options.newlineIsToken) { - throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); - } - - if (!options.callback) { - return diffLinesResultToPatch(diffLines(oldStr, newStr, options)); - } else { - const {callback} = options; - diffLines( - oldStr, - newStr, - { - ...options, - callback: (diff) => { - const patch = diffLinesResultToPatch(diff); - callback(patch); - } - } - ); - } - - function diffLinesResultToPatch(diff) { - // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays - // of lines containing trailing newline characters. We'll tidy up later... - - if(!diff) { - return; - } - - diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier - - function contextLines(lines) { - return lines.map(function(entry) { return ' ' + entry; }); - } - - let hunks = []; - let oldRangeStart = 0, newRangeStart = 0, curRange = [], - oldLine = 1, newLine = 1; - for (let i = 0; i < diff.length; i++) { - const current = diff[i], - lines = current.lines || splitLines(current.value); - current.lines = lines; - - if (current.added || current.removed) { - // If we have previous context, start with that - if (!oldRangeStart) { - const prev = diff[i - 1]; - oldRangeStart = oldLine; - newRangeStart = newLine; - - if (prev) { - curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; - oldRangeStart -= curRange.length; - newRangeStart -= curRange.length; - } - } - - // Output our changes - for (const line of lines) { - curRange.push((current.added ? '+' : '-') + line); - } - - // Track the updated file position - if (current.added) { - newLine += lines.length; - } else { - oldLine += lines.length; - } - } else { - // Identical context lines. Track line changes - if (oldRangeStart) { - // Close out any changes that have been output (or join overlapping) - if (lines.length <= options.context * 2 && i < diff.length - 2) { - // Overlapping - for (const line of contextLines(lines)) { - curRange.push(line); - } - } else { - // end the range and output - let contextSize = Math.min(lines.length, options.context); - for (const line of contextLines(lines.slice(0, contextSize))) { - curRange.push(line); - } - - let hunk = { - oldStart: oldRangeStart, - oldLines: (oldLine - oldRangeStart + contextSize), - newStart: newRangeStart, - newLines: (newLine - newRangeStart + contextSize), - lines: curRange - }; - hunks.push(hunk); - - oldRangeStart = 0; - newRangeStart = 0; - curRange = []; - } - } - oldLine += lines.length; - newLine += lines.length; - } - } - - // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add - // "\ No newline at end of file". - for (const hunk of hunks) { - for (let i = 0; i < hunk.lines.length; i++) { - if (hunk.lines[i].endsWith('\n')) { - hunk.lines[i] = hunk.lines[i].slice(0, -1); - } else { - hunk.lines.splice(i + 1, 0, '\\ No newline at end of file'); - i++; // Skip the line we just added, then continue iterating - } - } - } - - return { - oldFileName: oldFileName, newFileName: newFileName, - oldHeader: oldHeader, newHeader: newHeader, - hunks: hunks - }; - } -} - -export function formatPatch(diff) { - if (Array.isArray(diff)) { - return diff.map(formatPatch).join('\n'); - } - - const ret = []; - if (diff.oldFileName == diff.newFileName) { - ret.push('Index: ' + diff.oldFileName); - } - ret.push('==================================================================='); - ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); - ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); - - for (let i = 0; i < diff.hunks.length; i++) { - const hunk = diff.hunks[i]; - // Unified Diff Format quirk: If the chunk size is 0, - // the first number is one lower than one would expect. - // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 - if (hunk.oldLines === 0) { - hunk.oldStart -= 1; - } - if (hunk.newLines === 0) { - hunk.newStart -= 1; - } - ret.push( - '@@ -' + hunk.oldStart + ',' + hunk.oldLines - + ' +' + hunk.newStart + ',' + hunk.newLines - + ' @@' - ); - for (const line of hunk.lines) { - ret.push(line); - } - } - - return ret.join('\n') + '\n'; -} - -export function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - if (typeof options === 'function') { - options = {callback: options}; - } - - if (!options?.callback) { - const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); - if (!patchObj) { - return; - } - return formatPatch(patchObj); - } else { - const {callback} = options; - structuredPatch( - oldFileName, - newFileName, - oldStr, - newStr, - oldHeader, - newHeader, - { - ...options, - callback: patchObj => { - if (!patchObj) { - callback(); - } else { - callback(formatPatch(patchObj)); - } - } - } - ); - } -} - -export function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { - return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); -} - -/** - * Split `text` into an array of lines, including the trailing newline character (where present) - */ -function splitLines(text) { - const hasTrailingNl = text.endsWith('\n'); - const result = text.split('\n').map(line => line + '\n'); - if (hasTrailingNl) { - result.pop(); - } else { - result.push(result.pop().slice(0, -1)); - } - return result; -} diff --git a/src/patch/create.ts b/src/patch/create.ts new file mode 100644 index 000000000..138f95446 --- /dev/null +++ b/src/patch/create.ts @@ -0,0 +1,484 @@ +import {diffLines} from '../diff/line.js'; +import type { StructuredPatch, DiffLinesOptionsAbortable, DiffLinesOptionsNonabortable, AbortableDiffOptions, ChangeObject } from '../types.js'; + +type StructuredPatchCallbackAbortable = (patch: StructuredPatch | undefined) => void; +type StructuredPatchCallbackNonabortable = (patch: StructuredPatch) => void; + +interface _StructuredPatchOptionsAbortable extends Pick { + /** + * describes how many lines of context should be included. + * You can set this to `Number.MAX_SAFE_INTEGER` or `Infinity` to include the entire file content in one hunk. + * @default 4 + */ + context?: number, + callback?: StructuredPatchCallbackAbortable, +} +export type StructuredPatchOptionsAbortable = _StructuredPatchOptionsAbortable & AbortableDiffOptions; +export interface StructuredPatchOptionsNonabortable extends Pick { + context?: number, + callback?: StructuredPatchCallbackNonabortable, +} +interface StructuredPatchCallbackOptionAbortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: StructuredPatchCallbackAbortable; +} +interface StructuredPatchCallbackOptionNonabortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: StructuredPatchCallbackNonabortable; +} + +// Purely an implementation detail of diffLinesResultToPatch, which mutates the result of diffLines +// for convenience of implementation +interface ChangeObjectPlusLines extends Partial> { + value: string; + lines?: string[]; +} + +/** + * returns an object with an array of hunk objects. + * + * This method is similar to createTwoFilesPatch, but returns a data structure suitable for further processing. + * @param oldFileName String to be output in the filename section of the patch for the removals + * @param newFileName String to be output in the filename section of the patch for the additions + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchCallbackNonabortable +): undefined; +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsAbortable & StructuredPatchCallbackOptionAbortable +): undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsNonabortable & StructuredPatchCallbackOptionNonabortable +): undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: StructuredPatchOptionsAbortable +): StructuredPatch | undefined +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: StructuredPatchOptionsNonabortable +): StructuredPatch +export function structuredPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: StructuredPatchOptionsAbortable | StructuredPatchOptionsNonabortable | StructuredPatchCallbackNonabortable +): StructuredPatch | undefined { + let optionsObj: StructuredPatchOptionsAbortable | StructuredPatchOptionsNonabortable; + if (!options) { + optionsObj = {}; + } else if (typeof options === 'function') { + optionsObj = {callback: options}; + } else { + optionsObj = options; + } + + + if (typeof optionsObj.context === 'undefined') { + optionsObj.context = 4; + } + + // We copy this into its own variable to placate TypeScript, which thinks + // optionsObj.context might be undefined in the callbacks below. + const context = optionsObj.context; + + // @ts-expect-error (runtime check for something that is correctly a static type error) + if (optionsObj.newlineIsToken) { + throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); + } + + if (!optionsObj.callback) { + return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj as any)); + } else { + const {callback} = optionsObj; + diffLines( + oldStr, + newStr, + { + ...optionsObj, + callback: (diff) => { + const patch = diffLinesResultToPatch(diff); + // TypeScript is unhappy without the cast because it does not understand that `patch` may + // be undefined here only if `callback` is StructuredPatchCallbackAbortable: + (callback as any)(patch); + } + } + ); + } + + function diffLinesResultToPatch(diff: ChangeObjectPlusLines[] | undefined) { + // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays + // of lines containing trailing newline characters. We'll tidy up later... + + if(!diff) { + return; + } + + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines: string[]) { + return lines.map(function(entry) { return ' ' + entry; }); + } + + const hunks = []; + let oldRangeStart = 0, newRangeStart = 0, curRange: string[] = [], + oldLine = 1, newLine = 1; + for (let i = 0; i < diff.length; i++) { + const current = diff[i], + lines = current.lines || splitLines(current.value); + current.lines = lines; + + if (current.added || current.removed) { + // If we have previous context, start with that + if (!oldRangeStart) { + const prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = context > 0 ? contextLines(prev.lines!.slice(-context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + for (const line of lines) { + curRange.push((current.added ? '+' : '-') + line); + } + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= context * 2 && i < diff.length - 2) { + // Overlapping + for (const line of contextLines(lines)) { + curRange.push(line); + } + } else { + // end the range and output + const contextSize = Math.min(lines.length, context); + for (const line of contextLines(lines.slice(0, contextSize))) { + curRange.push(line); + } + + const hunk = { + oldStart: oldRangeStart, + oldLines: (oldLine - oldRangeStart + contextSize), + newStart: newRangeStart, + newLines: (newLine - newRangeStart + contextSize), + lines: curRange + }; + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add + // "\ No newline at end of file". + for (const hunk of hunks) { + for (let i = 0; i < hunk.lines.length; i++) { + if (hunk.lines[i].endsWith('\n')) { + hunk.lines[i] = hunk.lines[i].slice(0, -1); + } else { + hunk.lines.splice(i + 1, 0, '\\ No newline at end of file'); + i++; // Skip the line we just added, then continue iterating + } + } + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } +} + +/** + * creates a unified diff patch. + * @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`) + */ +export function formatPatch(patch: StructuredPatch | StructuredPatch[]): string { + if (Array.isArray(patch)) { + return patch.map(formatPatch).join('\n'); + } + + const ret = []; + if (patch.oldFileName == patch.newFileName) { + ret.push('Index: ' + patch.oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader)); + ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader)); + + for (let i = 0; i < patch.hunks.length; i++) { + const hunk = patch.hunks[i]; + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart -= 1; + } + if (hunk.newLines === 0) { + hunk.newStart -= 1; + } + ret.push( + '@@ -' + hunk.oldStart + ',' + hunk.oldLines + + ' +' + hunk.newStart + ',' + hunk.newLines + + ' @@' + ); + for (const line of hunk.lines) { + ret.push(line); + } + } + + return ret.join('\n') + '\n'; +} + +type CreatePatchCallbackAbortable = (patch: string | undefined) => void; +type CreatePatchCallbackNonabortable = (patch: string) => void; + +interface _CreatePatchOptionsAbortable extends Pick { + context?: number, + callback?: CreatePatchCallbackAbortable, +} +export type CreatePatchOptionsAbortable = _CreatePatchOptionsAbortable & AbortableDiffOptions; +export interface CreatePatchOptionsNonabortable extends Pick { + context?: number, + callback?: CreatePatchCallbackNonabortable, +} +interface CreatePatchCallbackOptionAbortable { + callback: CreatePatchCallbackAbortable; +} +interface CreatePatchCallbackOptionNonabortable { + callback: CreatePatchCallbackNonabortable; +} + +/** + * creates a unified diff patch by first computing a diff with `diffLines` and then serializing it to unified diff format. + * @param oldFileName String to be output in the filename section of the patch for the removals + * @param newFileName String to be output in the filename section of the patch for the additions + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchCallbackNonabortable +): undefined; +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable & CreatePatchCallbackOptionAbortable +): undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsNonabortable & CreatePatchCallbackOptionNonabortable +): undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable +): string | undefined +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsNonabortable +): string +export function createTwoFilesPatch( + oldFileName: string, + newFileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsAbortable | CreatePatchOptionsNonabortable | CreatePatchCallbackNonabortable +): string | undefined { + if (typeof options === 'function') { + options = {callback: options}; + } + + if (!options?.callback) { + const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options as any); + if (!patchObj) { + return; + } + return formatPatch(patchObj); + } else { + const {callback} = options; + structuredPatch( + oldFileName, + newFileName, + oldStr, + newStr, + oldHeader, + newHeader, + { + ...options, + callback: patchObj => { + if (!patchObj) { + (callback as CreatePatchCallbackAbortable)(undefined); + } else { + callback(formatPatch(patchObj)); + } + } + } + ); + } +} + +/** + * creates a unified diff patch. + * + * Just like createTwoFilesPatch, but with oldFileName being equal to newFileName. + * @param fileName String to be output in the filename section of the patch + * @param oldStr Original string value + * @param newStr New string value + * @param oldHeader Optional additional information to include in the old file header. + * @param newHeader Optional additional information to include in the new file header. + */ +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchCallbackNonabortable +): undefined; +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable & CreatePatchCallbackOptionAbortable +): undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsNonabortable & CreatePatchCallbackOptionNonabortable +): undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader: string | undefined, + newHeader: string | undefined, + options: CreatePatchOptionsAbortable +): string | undefined +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsNonabortable +): string +export function createPatch( + fileName: string, + oldStr: string, + newStr: string, + oldHeader?: string, + newHeader?: string, + options?: CreatePatchOptionsAbortable | CreatePatchOptionsNonabortable | CreatePatchCallbackNonabortable +): string | undefined { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options as any); +} + +/** + * Split `text` into an array of lines, including the trailing newline character (where present) + */ +function splitLines(text: string): string[] { + const hasTrailingNl = text.endsWith('\n'); + const result = text.split('\n').map(line => line + '\n'); + if (hasTrailingNl) { + result.pop(); + } else { + result.push( + (result.pop() as string).slice(0, -1) + ); + } + return result; +} diff --git a/src/patch/line-endings.js b/src/patch/line-endings.js deleted file mode 100644 index d1907b47a..000000000 --- a/src/patch/line-endings.js +++ /dev/null @@ -1,62 +0,0 @@ -export function unixToWin(patch) { - if (Array.isArray(patch)) { - return patch.map(unixToWin); - } - - return { - ...patch, - hunks: patch.hunks.map(hunk => ({ - ...hunk, - lines: hunk.lines.map( - (line, i) => - (line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\')) - ? line - : line + '\r' - ) - })) - }; -} - -export function winToUnix(patch) { - if (Array.isArray(patch)) { - return patch.map(winToUnix); - } - - return { - ...patch, - hunks: patch.hunks.map(hunk => ({ - ...hunk, - lines: hunk.lines.map(line => line.endsWith('\r') ? line.substring(0, line.length - 1) : line) - })) - }; -} - -/** - * Returns true if the patch consistently uses Unix line endings (or only involves one line and has - * no line endings). - */ -export function isUnix(patch) { - if (!Array.isArray(patch)) { patch = [patch]; } - return !patch.some( - index => index.hunks.some( - hunk => hunk.lines.some( - line => !line.startsWith('\\') && line.endsWith('\r') - ) - ) - ); -} - -/** - * Returns true if the patch uses Windows line endings and only Windows line endings. - */ -export function isWin(patch) { - if (!Array.isArray(patch)) { patch = [patch]; } - return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r')))) - && patch.every( - index => index.hunks.every( - hunk => hunk.lines.every( - (line, i) => line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\') - ) - ) - ); -} diff --git a/src/patch/line-endings.ts b/src/patch/line-endings.ts new file mode 100644 index 000000000..d36c7a595 --- /dev/null +++ b/src/patch/line-endings.ts @@ -0,0 +1,77 @@ +import type { StructuredPatch } from '../types.js'; + +export function unixToWin(patch: StructuredPatch): StructuredPatch; +export function unixToWin(patches: StructuredPatch[]): StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function unixToWin(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(patch)) { + // It would be cleaner if instead of the line below we could just write + // return patch.map(unixToWin) + // but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will + // refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the + // result would be incompatible with the overload signatures. + // See bug report at https://github.com/microsoft/TypeScript/issues/61398. + return patch.map(p => unixToWin(p)); + } + + return { + ...patch, + hunks: patch.hunks.map(hunk => ({ + ...hunk, + lines: hunk.lines.map( + (line, i) => + (line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\')) + ? line + : line + '\r' + ) + })) + }; +} + +export function winToUnix(patch: StructuredPatch): StructuredPatch; +export function winToUnix(patches: StructuredPatch[]): StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function winToUnix(patch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(patch)) { + // (See comment above equivalent line in unixToWin) + return patch.map(p => winToUnix(p)); + } + + return { + ...patch, + hunks: patch.hunks.map(hunk => ({ + ...hunk, + lines: hunk.lines.map(line => line.endsWith('\r') ? line.substring(0, line.length - 1) : line) + })) + }; +} + +/** + * Returns true if the patch consistently uses Unix line endings (or only involves one line and has + * no line endings). + */ +export function isUnix(patch: StructuredPatch | StructuredPatch[]): boolean { + if (!Array.isArray(patch)) { patch = [patch]; } + return !patch.some( + index => index.hunks.some( + hunk => hunk.lines.some( + line => !line.startsWith('\\') && line.endsWith('\r') + ) + ) + ); +} + +/** + * Returns true if the patch uses Windows line endings and only Windows line endings. + */ +export function isWin(patch: StructuredPatch | StructuredPatch[]): boolean { + if (!Array.isArray(patch)) { patch = [patch]; } + return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r')))) + && patch.every( + index => index.hunks.every( + hunk => hunk.lines.every( + (line, i) => line.startsWith('\\') || line.endsWith('\r') || hunk.lines[i + 1]?.startsWith('\\') + ) + ) + ); +} diff --git a/src/patch/parse.js b/src/patch/parse.ts similarity index 70% rename from src/patch/parse.js rename to src/patch/parse.ts index 61334b23b..4d134de8f 100755 --- a/src/patch/parse.js +++ b/src/patch/parse.ts @@ -1,23 +1,30 @@ -export function parsePatch(uniDiff) { - let diffstr = uniDiff.split(/\n/), - list = [], - i = 0; +import type { StructuredPatch } from '../types.js'; + +/** + * Parses a patch into structured data, in the same structure returned by `structuredPatch`. + * + * @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method. + */ +export function parsePatch(uniDiff: string): StructuredPatch[] { + const diffstr = uniDiff.split(/\n/), + list: Partial[] = []; + let i = 0; function parseIndex() { - let index = {}; + const index: Partial = {}; list.push(index); // Parse diff metadata while (i < diffstr.length) { - let line = diffstr[i]; + const line = diffstr[i]; // File header found, end parsing diff metadata - if ((/^(\-\-\-|\+\+\+|@@)\s/).test(line)) { + if ((/^(---|\+\+\+|@@)\s/).test(line)) { break; } // Diff index - let header = (/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/).exec(line); + const header = (/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/).exec(line); if (header) { index.index = header[1]; } @@ -34,8 +41,8 @@ export function parsePatch(uniDiff) { index.hunks = []; while (i < diffstr.length) { - let line = diffstr[i]; - if ((/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/).test(line)) { + const line = diffstr[i]; + if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) { break; } else if ((/^@@/).test(line)) { index.hunks.push(parseHunk()); @@ -49,17 +56,22 @@ export function parsePatch(uniDiff) { // Parses the --- and +++ headers, if none are found, no lines // are consumed. - function parseFileHeader(index) { + function parseFileHeader(index: Partial) { const fileHeader = (/^(---|\+\+\+)\s+(.*)\r?$/).exec(diffstr[i]); if (fileHeader) { - let keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; - const data = fileHeader[2].split('\t', 2); + const data = fileHeader[2].split('\t', 2), + header = (data[1] || '').trim(); let fileName = data[0].replace(/\\\\/g, '\\'); if ((/^".*"$/).test(fileName)) { fileName = fileName.substr(1, fileName.length - 2); } - index[keyPrefix + 'FileName'] = fileName; - index[keyPrefix + 'Header'] = (data[1] || '').trim(); + if (fileHeader[1] === '---') { + index.oldFileName = fileName; + index.oldHeader = header; + } else { + index.newFileName = fileName; + index.newHeader = header; + } i++; } @@ -68,16 +80,16 @@ export function parsePatch(uniDiff) { // Parses a hunk // This assumes that we are at the start of a hunk. function parseHunk() { - let chunkHeaderIndex = i, + const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); - let hunk = { + const hunk = { oldStart: +chunkHeader[1], oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], newStart: +chunkHeader[3], newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], - lines: [] + lines: [] as string[] }; // Unified Diff Format quirk: If the chunk size is 0, @@ -97,7 +109,7 @@ export function parsePatch(uniDiff) { i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || diffstr[i]?.startsWith('\\')); i++ ) { - let operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; + const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { hunk.lines.push(diffstr[i]); @@ -137,5 +149,5 @@ export function parsePatch(uniDiff) { parseIndex(); } - return list; + return list as StructuredPatch[]; } diff --git a/src/patch/reverse.js b/src/patch/reverse.js deleted file mode 100644 index e839eebaa..000000000 --- a/src/patch/reverse.js +++ /dev/null @@ -1,26 +0,0 @@ -export function reversePatch(structuredPatch) { - if (Array.isArray(structuredPatch)) { - return structuredPatch.map(reversePatch).reverse(); - } - - return { - ...structuredPatch, - oldFileName: structuredPatch.newFileName, - oldHeader: structuredPatch.newHeader, - newFileName: structuredPatch.oldFileName, - newHeader: structuredPatch.oldHeader, - hunks: structuredPatch.hunks.map(hunk => { - return { - oldLines: hunk.newLines, - oldStart: hunk.newStart, - newLines: hunk.oldLines, - newStart: hunk.oldStart, - lines: hunk.lines.map(l => { - if (l.startsWith('-')) { return `+${l.slice(1)}`; } - if (l.startsWith('+')) { return `-${l.slice(1)}`; } - return l; - }) - }; - }) - }; -} diff --git a/src/patch/reverse.ts b/src/patch/reverse.ts new file mode 100644 index 000000000..65a5abc37 --- /dev/null +++ b/src/patch/reverse.ts @@ -0,0 +1,36 @@ +import type { StructuredPatch } from '../types.js'; + +/** + * @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`). + * @returns a new structured patch which when applied will undo the original `patch`. + */ +export function reversePatch(structuredPatch: StructuredPatch): StructuredPatch; +export function reversePatch(structuredPatch: StructuredPatch[]): StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; +export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { + if (Array.isArray(structuredPatch)) { + // (See comment in unixToWin for why we need the pointless-looking anonymous function here) + return structuredPatch.map(patch => reversePatch(patch)).reverse(); + } + + return { + ...structuredPatch, + oldFileName: structuredPatch.newFileName, + oldHeader: structuredPatch.newHeader, + newFileName: structuredPatch.oldFileName, + newHeader: structuredPatch.oldHeader, + hunks: structuredPatch.hunks.map(hunk => { + return { + oldLines: hunk.newLines, + oldStart: hunk.newStart, + newLines: hunk.oldLines, + newStart: hunk.oldStart, + lines: hunk.lines.map(l => { + if (l.startsWith('-')) { return `+${l.slice(1)}`; } + if (l.startsWith('+')) { return `-${l.slice(1)}`; } + return l; + }) + }; + }) + }; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..85b842c96 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,242 @@ +export interface ChangeObject { + /** + * The concatenated content of all the tokens represented by this change object - i.e. generally the text that is either added, deleted, or common, as a single string. + * In cases where tokens are considered common but are non-identical (e.g. because an option like `ignoreCase` or a custom `comparator` was used), the value from the *new* string will be provided here. + */ + value: ValueT; + /** + * true if the value was inserted into the new string, otherwise false + */ + added: boolean; + /** + * true if the value was removed from the old string, otherwise false + */ + removed: boolean; + /** + * How many tokens (e.g. chars for `diffChars`, lines for `diffLines`) the value in the change object consists of + */ + count: number; +} + +// Name "Change" is used here for consistency with the previous type definitions from +// DefinitelyTyped. I would *guess* this is probably the single most common type for people to +// explicitly reference by name in their own code, so keeping its name consistent is valuable even +// though the names of many other types are inconsistent with the old DefinitelyTyped names. +export type Change = ChangeObject; +export type ArrayChange = ChangeObject; + +export interface CommonDiffOptions { + /** + * If `true`, the array of change objects returned will contain one change object per token (e.g. one per line if calling `diffLines`), instead of runs of consecutive tokens that are all added / all removed / all conserved being combined into a single change object. + */ + oneChangePerToken?: boolean, +} + +export interface TimeoutOption { + /** + * A number of milliseconds after which the diffing algorithm will abort and return `undefined`. + * Supported by the same functions as `maxEditLength`. + */ + timeout: number; +} + +export interface MaxEditLengthOption { + /** + * A number specifying the maximum edit distance to consider between the old and new texts. + * You can use this to limit the computational cost of diffing large, very different texts by giving up early if the cost will be huge. + * This option can be passed either to diffing functions (`diffLines`, `diffChars`, etc) or to patch-creation function (`structuredPatch`, `createPatch`, etc), all of which will indicate that the max edit length was reached by returning `undefined` instead of whatever they'd normally return. + */ + maxEditLength: number; +} + +export type AbortableDiffOptions = TimeoutOption | MaxEditLengthOption; + +export type DiffCallbackNonabortable = (result: ChangeObject[]) => void; +export type DiffCallbackAbortable = (result: ChangeObject[] | undefined) => void; + +export interface CallbackOptionNonabortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: DiffCallbackNonabortable +} +export interface CallbackOptionAbortable { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback: DiffCallbackAbortable +} + +interface DiffArraysOptions extends CommonDiffOptions { + comparator?: (a: T, b: T) => boolean, +} +export interface DiffArraysOptionsNonabortable extends DiffArraysOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffArraysOptionsAbortable = DiffArraysOptions & AbortableDiffOptions & Partial> + + +interface DiffCharsOptions extends CommonDiffOptions { + /** + * If `true`, the uppercase and lowercase forms of a character are considered equal. + * @default false + */ + ignoreCase?: boolean; +} +export interface DiffCharsOptionsNonabortable extends DiffCharsOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffCharsOptionsAbortable = DiffCharsOptions & AbortableDiffOptions & Partial> + +interface DiffLinesOptions extends CommonDiffOptions { + /** + * `true` to remove all trailing CR (`\r`) characters before performing the diff. + * This helps to get a useful diff when diffing UNIX text files against Windows text files. + * @default false + */ + stripTrailingCr?: boolean, + /** + * `true` to treat the newline character at the end of each line as its own token. + * This allows for changes to the newline structure to occur independently of the line content and to be treated as such. + * In general this is the more human friendly form of `diffLines`; the default behavior with this option turned off is better suited for patches and other computer friendly output. + * + * Note that while using `ignoreWhitespace` in combination with `newlineIsToken` is not an error, results may not be as expected. + * With `ignoreWhitespace: true` and `newlineIsToken: false`, changing a completely empty line to contain some spaces is treated as a non-change, but with `ignoreWhitespace: true` and `newlineIsToken: true`, it is treated as an insertion. + * This is because the content of a completely blank line is not a token at all in `newlineIsToken` mode. + * + * @default false + */ + newlineIsToken?: boolean, + /** + * `true` to ignore a missing newline character at the end of the last line when comparing it to other lines. + * (By default, the line `'b\n'` in text `'a\nb\nc'` is not considered equal to the line `'b'` in text `'a\nb'`; this option makes them be considered equal.) + * Ignored if `ignoreWhitespace` or `newlineIsToken` are also true. + * @default false + */ + ignoreNewlineAtEof?: boolean, + /** + * `true` to ignore leading and trailing whitespace characters when checking if two lines are equal. + * @default false + */ + ignoreWhitespace?: boolean, +} +export interface DiffLinesOptionsNonabortable extends DiffLinesOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffLinesOptionsAbortable = DiffLinesOptions & AbortableDiffOptions & Partial> + + +interface DiffWordsOptions extends CommonDiffOptions { + /** + * Same as in `diffChars`. + * @default false + */ + ignoreCase?: boolean + + /** + * An optional [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) object (which must have a `granularity` of `'word'`) for `diffWords` to use to split the text into words. + * + * Note that this is (deliberately) incorrectly typed as `any` to avoid users whose `lib` & `target` settings in tsconfig.json are older than es2022 getting type errors when they build about `Intl.Segmenter` not existing. + * This is kind of ugly, since it makes the type declarations worse for users who genuinely use this feature, but seemed worth it to avoid the majority of the library's users (who probably do not use this particular option) getting confusing errors and being forced to change their `lib` to es2022 (even if their own code doesn't use any es2022 functions). + * + * By default, `diffWords` does not use an `Intl.Segmenter`, just some regexes for splitting text into words. This will tend to give worse results than `Intl.Segmenter` would, but ensures the results are consistent across environments; `Intl.Segmenter` behaviour is only loosely specced and the implementations in browsers could in principle change dramatically in future. If you want to use `diffWords` with an `Intl.Segmenter` but ensure it behaves the same whatever environment you run it in, use an `Intl.Segmenter` polyfill instead of the JavaScript engine's native `Intl.Segmenter` implementation. + * + * Using an `Intl.Segmenter` should allow better word-level diffing of non-English text than the default behaviour. For instance, `Intl.Segmenter`s can generally identify via built-in dictionaries which sequences of adjacent Chinese characters form words, allowing word-level diffing of Chinese. By specifying a language when instantiating the segmenter (e.g. `new Intl.Segmenter('sv', {granularity: 'word'})`) you can also support language-specific rules, like treating Swedish's colon separated contractions (like *k:a* for *kyrka*) as single words; by default this would be seen as two words separated by a colon. + */ + intlSegmenter?: any, +} +export interface DiffWordsOptionsNonabortable extends DiffWordsOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffWordsOptionsAbortable = DiffWordsOptions & AbortableDiffOptions & Partial> + + +interface DiffSentencesOptions extends CommonDiffOptions {} +export interface DiffSentencesOptionsNonabortable extends DiffSentencesOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffSentencesOptionsAbortable = DiffSentencesOptions & AbortableDiffOptions & Partial> + + +interface DiffJsonOptions extends CommonDiffOptions { + /** + * A value to replace `undefined` with. Ignored if a `stringifyReplacer` is provided. + */ + undefinedReplacement?: any, + /** + * A custom replacer function. + * Operates similarly to the `replacer` parameter to [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter), but must be a function. + */ + stringifyReplacer?: (k: string, v: any) => any, +} +export interface DiffJsonOptionsNonabortable extends DiffJsonOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffJsonOptionsAbortable = DiffJsonOptions & AbortableDiffOptions & Partial> + + +interface DiffCssOptions extends CommonDiffOptions {} +export interface DiffCssOptionsNonabortable extends DiffCssOptions { + /** + * If provided, the diff will be computed in async mode to avoid blocking the event loop while the diff is calculated. + * The value of the `callback` option should be a function and will be passed the computed diff or patch as its first argument. + */ + callback?: DiffCallbackNonabortable +} +export type DiffCssOptionsAbortable = DiffJsonOptions & AbortableDiffOptions & Partial> + + +/** + * Note that this contains the union of ALL options accepted by any of the built-in diffing + * functions. The README notes which options are usable which functions. Using an option with a + * diffing function that doesn't support it might yield unreasonable results. + */ +export type AllDiffOptions = + DiffArraysOptions & + DiffCharsOptions & + DiffWordsOptions & + DiffLinesOptions & + DiffJsonOptions; + +export interface StructuredPatch { + oldFileName: string, + newFileName: string, + oldHeader: string | undefined, + newHeader: string | undefined, + hunks: StructuredPatchHunk[], + index?: string, +} + +export interface StructuredPatchHunk { + oldStart: number, + oldLines: number, + newStart: number, + newLines: number, + lines: string[], +} diff --git a/src/util/array.js b/src/util/array.ts similarity index 67% rename from src/util/array.js rename to src/util/array.ts index 5de3cab9d..602714aab 100644 --- a/src/util/array.js +++ b/src/util/array.ts @@ -1,4 +1,4 @@ -export function arrayEqual(a, b) { +export function arrayEqual(a: any[], b: any[]): boolean { if (a.length !== b.length) { return false; } @@ -6,7 +6,7 @@ export function arrayEqual(a, b) { return arrayStartsWith(a, b); } -export function arrayStartsWith(array, start) { +export function arrayStartsWith(array: any[], start: any[]): boolean { if (start.length > array.length) { return false; } diff --git a/src/util/distance-iterator.js b/src/util/distance-iterator.ts similarity index 87% rename from src/util/distance-iterator.js rename to src/util/distance-iterator.ts index 1ffe6413d..e2fe316cc 100644 --- a/src/util/distance-iterator.js +++ b/src/util/distance-iterator.ts @@ -1,13 +1,13 @@ // Iterator that traverses in the range of [min, max], stepping // by distance from a given start position. I.e. for [0, 4], with // start of 2, this will iterate 2, 3, 1, 4, 0. -export default function(start, minLine, maxLine) { +export default function(start: number, minLine: number, maxLine: number): () => number | undefined { let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1; - return function iterator() { + return function iterator(): number | undefined { if (wantForward && !forwardExhausted) { if (backwardExhausted) { localOffset++; @@ -41,5 +41,6 @@ export default function(start, minLine, maxLine) { // We tried to fit hunk before text beginning and beyond text length, then // hunk can't fit on the text. Return undefined + return undefined; }; } diff --git a/src/util/params.js b/src/util/params.js deleted file mode 100644 index 07e03e59e..000000000 --- a/src/util/params.js +++ /dev/null @@ -1,13 +0,0 @@ -export function generateOptions(options, defaults) { - if (typeof options === 'function') { - defaults.callback = options; - } else if (options) { - for (let name in options) { - /* istanbul ignore else */ - if (options.hasOwnProperty(name)) { - defaults[name] = options[name]; - } - } - } - return defaults; -} diff --git a/src/util/params.ts b/src/util/params.ts new file mode 100644 index 000000000..09911cbc7 --- /dev/null +++ b/src/util/params.ts @@ -0,0 +1,16 @@ +export function generateOptions( + options: {[key: string]: any} | ((_: unknown) => void), + defaults: any +): object { + if (typeof options === 'function') { + defaults.callback = options; + } else if (options) { + for (const name in options) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(options, name)) { + defaults[name] = options[name]; + } + } + } + return defaults; +} diff --git a/src/util/string.js b/src/util/string.ts similarity index 79% rename from src/util/string.js rename to src/util/string.ts index 80cf54786..9be0603ec 100644 --- a/src/util/string.js +++ b/src/util/string.ts @@ -1,4 +1,4 @@ -export function longestCommonPrefix(str1, str2) { +export function longestCommonPrefix(str1: string, str2: string): string { let i; for (i = 0; i < str1.length && i < str2.length; i++) { if (str1[i] != str2[i]) { @@ -8,7 +8,7 @@ export function longestCommonPrefix(str1, str2) { return str1.slice(0, i); } -export function longestCommonSuffix(str1, str2) { +export function longestCommonSuffix(str1: string, str2: string): string { let i; // Unlike longestCommonPrefix, we need a special case to handle all scenarios @@ -26,14 +26,14 @@ export function longestCommonSuffix(str1, str2) { return str1.slice(-i); } -export function replacePrefix(string, oldPrefix, newPrefix) { +export function replacePrefix(string: string, oldPrefix: string, newPrefix: string): string { if (string.slice(0, oldPrefix.length) != oldPrefix) { throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`); } return newPrefix + string.slice(oldPrefix.length); } -export function replaceSuffix(string, oldSuffix, newSuffix) { +export function replaceSuffix(string: string, oldSuffix: string, newSuffix: string): string { if (!oldSuffix) { return string + newSuffix; } @@ -44,20 +44,20 @@ export function replaceSuffix(string, oldSuffix, newSuffix) { return string.slice(0, -oldSuffix.length) + newSuffix; } -export function removePrefix(string, oldPrefix) { +export function removePrefix(string: string, oldPrefix: string): string { return replacePrefix(string, oldPrefix, ''); } -export function removeSuffix(string, oldSuffix) { +export function removeSuffix(string: string, oldSuffix: string): string { return replaceSuffix(string, oldSuffix, ''); } -export function maximumOverlap(string1, string2) { +export function maximumOverlap(string1: string, string2: string): string { return string2.slice(0, overlapCount(string1, string2)); } // Nicked from https://stackoverflow.com/a/60422853/1709587 -function overlapCount(a, b) { +function overlapCount(a: string, b: string): number { // Deal with cases where the strings differ in length let startA = 0; if (a.length > b.length) { startA = a.length - b.length; } @@ -66,7 +66,7 @@ function overlapCount(a, b) { // Create a back-reference for each index // that should be followed in case of a mismatch. // We only need B to make these references: - let map = Array(endB); + const map = Array(endB); let k = 0; // Index that lags behind j map[0] = 0; for (let j = 1; j < endB; j++) { @@ -91,18 +91,18 @@ function overlapCount(a, b) { /** * Returns true if the string consistently uses Windows line endings. */ -export function hasOnlyWinLineEndings(string) { +export function hasOnlyWinLineEndings(string: string): boolean { return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/); } /** * Returns true if the string consistently uses Unix line endings. */ -export function hasOnlyUnixLineEndings(string) { +export function hasOnlyUnixLineEndings(string: string): boolean { return !string.includes('\r\n') && string.includes('\n'); } -export function trailingWs(string) { +export function trailingWs(string: string): string { // Yes, this looks overcomplicated and dumb - why not replace the whole function with // return string match(/\s*$/)[0] // you ask? Because: @@ -123,7 +123,8 @@ export function trailingWs(string) { return string.substring(i + 1); } -export function leadingWs(string) { +export function leadingWs(string: string): string { // Thankfully the annoying considerations described in trailingWs don't apply here: - return string.match(/^\s*/)[0]; + const match = string.match(/^\s*/); + return match ? match[0] : ''; } diff --git a/test-d/diffCharsOverloads.test-d.ts b/test-d/diffCharsOverloads.test-d.ts new file mode 100644 index 000000000..bb29e9943 --- /dev/null +++ b/test-d/diffCharsOverloads.test-d.ts @@ -0,0 +1,34 @@ +import {expectType} from 'tsd'; +import {ChangeObject, diffChars} from '../libesm/index.js'; + +const result1 = diffChars('foo', 'bar', {ignoreCase: true}); +expectType[]>(result1); + +const result2 = diffChars('foo', 'bar'); +expectType[]>(result2); + +const result3 = diffChars('foo', 'bar', {timeout: 100}); +expectType[] | undefined>(result3); + +const result4 = diffChars('foo', 'bar', {maxEditLength: 100}); +expectType[] | undefined>(result4); + +const result5 = diffChars('foo', 'bar', cbResult => { + expectType[]>(cbResult) +}); +expectType(result5); + +const result6 = diffChars('foo', 'bar', { + callback: cbResult => { + expectType[]>(cbResult); + } +}); +expectType(result6); + +const result7 = diffChars('foo', 'bar', { + timeout: 100, + callback: cbResult => { + expectType[] | undefined>(cbResult) + } +}); +expectType(result7); diff --git a/test-d/originalDefinitelyTypedTests.test-d.ts b/test-d/originalDefinitelyTypedTests.test-d.ts new file mode 100644 index 000000000..9e56efab0 --- /dev/null +++ b/test-d/originalDefinitelyTypedTests.test-d.ts @@ -0,0 +1,186 @@ +/** + * This file was copied from + * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/diff/diff-tests.ts + * then tweaked to work with tsd. + */ + +import {expectType} from 'tsd'; +import Diff, { Change, StructuredPatch } from "../libesm/index.js"; + +const one = "beep boop"; +const other = "beep boob blah"; + +let changes = Diff.diffChars(one, other); +examineChanges(changes); + +expectType(Diff.diffChars(one, other, { + callback: (value) => { + expectType(value); + }, +})); +expectType(Diff.diffChars(one, other, (value) => { + expectType(value); +})); +Diff.diffWords("吾輩は猫である。名前はまだ無い。", "吾輩は猫である。名前はたぬき。", { + intlSegmenter: new Intl.Segmenter("ja-JP", { granularity: "word" }), +}); +expectType( + Diff.diffLines( + "line\nold value\nline", + "line\nnew value\nline", + { + stripTrailingCr: true, + ignoreNewlineAtEof: true, + maxEditLength: 1, + oneChangePerToken: true, + }, + ) +); +expectType( + Diff.createPatch("filename", "A", "a", undefined, undefined, { + callback: (value) => { + expectType(value); + }, + }) +); + +const diffArraysResult = Diff.diffArrays(["a", "b", "c"], ["a", "c", "d"]); +diffArraysResult.forEach(result => { + expectType(result.added); + expectType(result.removed); + expectType(result.value); + expectType(result.count); +}); + +interface DiffObj { + value: number; +} +const a: DiffObj = { value: 0 }; +const b: DiffObj = { value: 1 }; +const c: DiffObj = { value: 2 }; +const d: DiffObj = { value: 3 }; +const arrayOptions: Diff.DiffArraysOptionsNonabortable = { + comparator: (left, right) => { + return left.value === right.value; + }, +}; +const arrayChanges = Diff.diffArrays([a, b, c], [a, b, d], arrayOptions); +arrayChanges.forEach(result => { + expectType(result.added) + expectType(result.removed) + expectType(result.value) + expectType(result.count) +}); + +// -------------------------- + +class LineDiffWithoutWhitespace extends Diff.Diff { + tokenize(value: string): any { + return value.split(/^/m); + } + + equals(left: string, right: string): boolean { + return left.trim() === right.trim(); + } +} + +const obj = new LineDiffWithoutWhitespace(); +changes = obj.diff(one, other); +examineChanges(changes); + +function examineChanges(diff: Diff.Change[]) { + diff.forEach(part => { + expectType(part.added); + expectType(part.removed); + expectType(part.value); + expectType(part.count); + }); +} + +function verifyPatchMethods(oldStr: string, newStr: string, uniDiff: Diff.StructuredPatch) { + const verifyPatch = Diff.parsePatch( + Diff.createTwoFilesPatch("oldFile.ts", "newFile.ts", oldStr, newStr, "old", "new", { + context: 1, + stripTrailingCr: true, + }), + ); + + if ( + JSON.stringify(verifyPatch[0], Object.keys(verifyPatch[0]).sort()) + !== JSON.stringify(uniDiff, Object.keys(uniDiff).sort()) + ) { + throw new Error("Patch did not match uniDiff"); + } +} +function verifyApplyMethods(oldStr: string, newStr: string, uniDiffStr: string) { + const uniDiff = Diff.parsePatch(uniDiffStr)[0]; + const verifyApply = [Diff.applyPatch(oldStr, uniDiff), Diff.applyPatch(oldStr, [uniDiff])]; + const options: Diff.ApplyPatchesOptions = { + loadFile(index, callback) { + expectType(index); + callback(undefined, one); + }, + patched(index, content) { + expectType(index); + if (content !== false) { + verifyApply.push(content); + } + }, + complete(err) { + if (err) { + throw err; + } + + verifyApply.forEach(result => { + if (result !== newStr) { + throw new Error("Result did not match newStr"); + } + }); + }, + compareLine(_, line, operator, patchContent) { + if (operator === " ") { + return true; + } + return line === patchContent; + }, + fuzzFactor: 0, + }; + Diff.applyPatches([uniDiff], options); + Diff.applyPatches(uniDiffStr, options); +} + +const uniDiffPatch = Diff.structuredPatch("oldFile.ts", "newFile.ts", one, other, "old", "new", { + context: 1, +}); +verifyPatchMethods(one, other, uniDiffPatch); + +const formatted: string = Diff.formatPatch(uniDiffPatch); + +const uniDiffStr = Diff.createPatch("file.ts", one, other, "old", "new", { context: 1 }); +verifyApplyMethods(one, other, uniDiffStr); + +const file1 = "line1\nline2\nline3\nline4\n"; +const file2 = "line1\nline2\nline5\nline4\n"; +const patch = Diff.structuredPatch("file1", "file2", file1, file2); +expectType(patch); +const reversedPatch = Diff.reversePatch(patch); +expectType(reversedPatch) +const verifyPatch = Diff.parsePatch( + Diff.createTwoFilesPatch("oldFile.ts", "newFile.ts", "old content", "new content", "old", "new", { + context: 1, + }), +); +expectType(verifyPatch) + +const wordDiff = new Diff.Diff(); +wordDiff.equals = function(left, right, options) { + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes("\n")) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes("\n")) { + right = right.trim(); + } + } + return Diff.Diff.prototype.equals.call(this, left, right, options); +}; diff --git a/test/convert/dmp.js b/test/convert/dmp.js index c86c08423..e5591294e 100644 --- a/test/convert/dmp.js +++ b/test/convert/dmp.js @@ -1,5 +1,5 @@ -import {convertChangesToDMP} from '../../lib/convert/dmp'; -import {diffChars} from '../../lib/diff/character'; +import {convertChangesToDMP} from '../../libesm/convert/dmp.js'; +import {diffChars} from '../../libesm/diff/character.js'; import {expect} from 'chai'; diff --git a/test/diff/array.js b/test/diff/array.js index d971ebddb..35c56ba87 100644 --- a/test/diff/array.js +++ b/test/diff/array.js @@ -1,4 +1,4 @@ -import {diffArrays} from '../../lib/diff/array'; +import {diffArrays} from '../../libesm/diff/array.js'; import {expect} from 'chai'; diff --git a/test/diff/character.js b/test/diff/character.js index e3aef9a8c..e999566c5 100644 --- a/test/diff/character.js +++ b/test/diff/character.js @@ -1,5 +1,5 @@ -import {diffChars} from '../../lib/diff/character'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffChars} from '../../libesm/diff/character.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/diff/css.js b/test/diff/css.js index 2b7277a22..c7636db57 100644 --- a/test/diff/css.js +++ b/test/diff/css.js @@ -1,5 +1,5 @@ -import {diffCss} from '../../lib/diff/css'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffCss} from '../../libesm/diff/css.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/diff/json.js b/test/diff/json.js index 332076d5f..4f7255a0d 100644 --- a/test/diff/json.js +++ b/test/diff/json.js @@ -1,5 +1,5 @@ -import {diffJson, canonicalize} from '../../lib/diff/json'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffJson, canonicalize} from '../../libesm/diff/json.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; @@ -117,25 +117,25 @@ describe('diff/json', function() { describe('#canonicalize', function() { it('should put the keys in canonical order', function() { - expect(getKeys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); + expect(Object.keys(canonicalize({b: 456, a: 123}))).to.eql(['a', 'b']); }); it('should dive into nested objects', function() { const canonicalObj = canonicalize({b: 456, a: {d: 123, c: 456}}); - expect(getKeys(canonicalObj.a)).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a)).to.eql(['c', 'd']); }); it('should dive into nested arrays', function() { const canonicalObj = canonicalize({b: 456, a: [789, {d: 123, c: 456}]}); - expect(getKeys(canonicalObj.a[1])).to.eql(['c', 'd']); + expect(Object.keys(canonicalObj.a[1])).to.eql(['c', 'd']); }); it('should handle circular references correctly', function() { const obj = {b: 456}; obj.a = obj; const canonicalObj = canonicalize(obj); - expect(getKeys(canonicalObj)).to.eql(['a', 'b']); - expect(getKeys(canonicalObj.a)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj)).to.eql(['a', 'b']); + expect(Object.keys(canonicalObj.a)).to.eql(['a', 'b']); }); it('should accept a custom JSON.stringify() replacer function', function() { @@ -144,8 +144,8 @@ describe('diff/json', function() { {a: /foo/} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": {}\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": {}\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -155,8 +155,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof RegExp ? v.toString() : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": "/foo/gi"\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "/foo/gi"\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -166,8 +166,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 1, value: ' \"a\": "Error: ohaider"\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 1, value: ' "a": "Error: ohaider"\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); @@ -177,8 +177,8 @@ describe('diff/json', function() { {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} )).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', added: false, removed: true }, - { count: 3, value: ' \"a\": [\n "Error: ohaider"\n ]\n', added: true, removed: false }, + { count: 1, value: ' "a": 123\n', added: false, removed: true }, + { count: 3, value: ' "a": [\n "Error: ohaider"\n ]\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ]); }); @@ -193,8 +193,8 @@ describe('diff/json', function() { ).to.deep.equal( [ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"foo\": "123a"\n', added: false, removed: true }, - { count: 1, value: ' \"foo\": "123x"\n', added: true, removed: false }, + { count: 1, value: ' "foo": "123a"\n', added: false, removed: true }, + { count: 1, value: ' "foo": "123x"\n', added: true, removed: false }, { count: 1, value: '}', removed: false, added: false } ] ); @@ -262,20 +262,10 @@ describe('diff/json', function() { }).not.to['throw'](); expect(diff).to.eql([ { count: 1, value: '{\n', removed: false, added: false }, - { count: 1, value: ' \"a\": 123\n', removed: true, added: false }, - { count: 1, value: ' \"b\": 456\n', removed: false, added: true }, + { count: 1, value: ' "a": 123\n', removed: true, added: false }, + { count: 1, value: ' "b": 456\n', removed: false, added: true }, { count: 1, value: '}', removed: false, added: false } ]); }); }); }); - -function getKeys(obj) { - const keys = []; - for (let key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys; -} diff --git a/test/diff/line.js b/test/diff/line.js index 7ce78a68b..cdaf08d60 100644 --- a/test/diff/line.js +++ b/test/diff/line.js @@ -1,5 +1,5 @@ -import {diffLines, diffTrimmedLines} from '../../lib/diff/line'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffLines, diffTrimmedLines} from '../../libesm/diff/line.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/diff/sentence.js b/test/diff/sentence.js index 1da79c161..a1d34e0e7 100644 --- a/test/diff/sentence.js +++ b/test/diff/sentence.js @@ -1,5 +1,5 @@ -import {diffSentences, sentenceDiff} from '../../lib/diff/sentence'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {diffSentences, sentenceDiff} from '../../libesm/diff/sentence.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/diff/word.js b/test/diff/word.js index 418a8ae57..656ee6d7c 100644 --- a/test/diff/word.js +++ b/test/diff/word.js @@ -1,5 +1,5 @@ -import {wordDiff, diffWords, diffWordsWithSpace} from '../../lib/diff/word'; -import {convertChangesToXML} from '../../lib/convert/xml'; +import {wordDiff, diffWords, diffWordsWithSpace} from '../../libesm/diff/word.js'; +import {convertChangesToXML} from '../../libesm/convert/xml.js'; import {expect} from 'chai'; diff --git a/test/index.js b/test/index.js index 100a867c8..c125cfe99 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -import * as Diff from '../lib'; +import * as Diff from 'diff'; import {expect} from 'chai'; diff --git a/test/patch/apply.js b/test/patch/apply.js index e93894160..c789488ec 100755 --- a/test/patch/apply.js +++ b/test/patch/apply.js @@ -1,7 +1,7 @@ -import {applyPatch, applyPatches} from '../../lib/patch/apply'; -import {parsePatch} from '../../lib/patch/parse'; -import {createPatch} from '../../lib/patch/create'; -import {structuredPatch} from '../../lib/patch/create'; +import {applyPatch, applyPatches} from '../../libesm/patch/apply.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; +import {createPatch} from '../../libesm/patch/create.js'; +import {structuredPatch} from '../../libesm/patch/create.js'; import {expect} from 'chai'; diff --git a/test/patch/create.js b/test/patch/create.js index 469f8df1a..862b3b7bf 100644 --- a/test/patch/create.js +++ b/test/patch/create.js @@ -1,6 +1,6 @@ -import {diffWords} from '../../lib'; -import {createPatch, createTwoFilesPatch, formatPatch, structuredPatch} from '../../lib/patch/create'; -import {parsePatch} from '../../lib/patch/parse'; +import {diffWords} from 'diff'; +import {createPatch, createTwoFilesPatch, formatPatch, structuredPatch} from '../../libesm/patch/create.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; import {expect} from 'chai'; @@ -702,7 +702,7 @@ describe('patch/create', function() { + '+line\n' + '\\ No newline at end of file\n'; - const diffResult = createPatch('testFileName', 'line \n\ line', 'line\n\line', undefined, undefined, {ignoreWhitespace: false}); + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: false}); expect(diffResult).to.equal(expectedResult); }); @@ -713,7 +713,7 @@ describe('patch/create', function() { + '--- testFileName\n' + '+++ testFileName\n'; - const diffResult = createPatch('testFileName', 'line \n\ line', 'line\n\line', undefined, undefined, {ignoreWhitespace: true}); + const diffResult = createPatch('testFileName', 'line \n line', 'line\nline', undefined, undefined, {ignoreWhitespace: true}); expect(diffResult).to.equal(expectedResult); }); }); @@ -794,9 +794,9 @@ describe('patch/create', function() { + '--- foo\n' + '+++ bar\n' + '@@ -1,2 +1,2 @@\n' - + '\-line\n' - + '\+line\r\n' - + '\ line\n' + + '-line\n' + + '+line\r\n' + + ' line\n' + '\\ No newline at end of file\n'; expect(createTwoFilesPatch( 'foo', diff --git a/test/patch/line-endings.js b/test/patch/line-endings.js index 5fe47586c..19d318b8e 100644 --- a/test/patch/line-endings.js +++ b/test/patch/line-endings.js @@ -1,6 +1,6 @@ -import {parsePatch} from '../../lib/patch/parse'; -import {formatPatch} from '../../lib/patch/create'; -import {winToUnix, unixToWin, isWin, isUnix} from '../../lib/patch/line-endings'; +import {parsePatch} from '../../libesm/patch/parse.js'; +import {formatPatch} from '../../libesm/patch/create.js'; +import {winToUnix, unixToWin, isWin, isUnix} from '../../libesm/patch/line-endings.js'; import {expect} from 'chai'; diff --git a/test/patch/parse.js b/test/patch/parse.js index 508935935..c06c26169 100644 --- a/test/patch/parse.js +++ b/test/patch/parse.js @@ -1,5 +1,5 @@ -import {parsePatch} from '../../lib/patch/parse'; -import {createPatch} from '../../lib/patch/create'; +import {parsePatch} from '../../libesm/patch/parse.js'; +import {createPatch} from '../../libesm/patch/create.js'; import {expect} from 'chai'; diff --git a/test/patch/reverse.js b/test/patch/reverse.js index 1781aa1dd..d151b4396 100644 --- a/test/patch/reverse.js +++ b/test/patch/reverse.js @@ -1,7 +1,7 @@ -import {applyPatch} from '../../lib/patch/apply'; -import {structuredPatch, formatPatch} from '../../lib/patch/create'; -import {reversePatch} from '../../lib/patch/reverse'; -import {parsePatch} from '../../lib/patch/parse'; +import {applyPatch} from '../../libesm/patch/apply.js'; +import {structuredPatch, formatPatch} from '../../libesm/patch/create.js'; +import {reversePatch} from '../../libesm/patch/reverse.js'; +import {parsePatch} from '../../libesm/patch/parse.js'; import {expect} from 'chai'; diff --git a/test/util/string.js b/test/util/string.js index df151b322..30e829935 100644 --- a/test/util/string.js +++ b/test/util/string.js @@ -1,4 +1,4 @@ -import {longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap} from '../../lib/util/string'; +import {longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap} from '../../libesm/util/string.js'; import {expect} from 'chai'; describe('#longestCommonPrefix', function() { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..87a5c8d0a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "include": ["src/*.ts", "src/**/*.ts"], + "compilerOptions": { + "rootDir": "src/", + "target": "es5", + "lib": [ + "es2022" + ], + "declaration": true, + "skipLibCheck": true, + + // Options below enabled per recommendation at + // https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-a-library + "strict": true, + "declarationMap": true, + // The same docs page recommends setting + // "verbatimModuleSyntax": true, + // but this totally breaks the CJS build and a footnote observes that "Any configuration + // that produces both an ESM and a CJS output from the same source file is fundamentally + // incompatible with" this setting. I don't really know WTF the authors of those docs were + // thinking, because the recommendation to turn this setting on is from the section + // specifically for "a library author" who wants to ensure their code works "under all + // possible library consumer compilation settings" - i.e. a person who is essentially 100% + // guaranteed to be running both an ESM and CJS build. So how can the recommendation to + // turn this setting on possible be even remotely sane? Beats me. ¯\_(ツ)_/¯ + // I've done the best I can by using the @typescript-eslint/consistent-type-imports and + // @typescript-eslint/consistent-type-exports ESLint rules to enforce SOME of what this + // setting would have enforced, though I dunno if I'm enforcing the bits that motivated the + // recommendation to turn it on. + } +} diff --git a/yarn.lock b/yarn.lock index 201e96e18..a6dc77c8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,23 +10,39 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@babel/cli@^7.26.4": - version "7.26.4" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.26.4.tgz#4101ff8ee5de8447a6c395397a97921056411d20" - integrity sha512-+mORf3ezU3p3qr+82WvJSnQNE1GAYeoCfEv4fik6B5/2cvKZ75AX8oawWQdXtM9MmndooQj15Jr9kelRFWsuRw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - commander "^6.2.0" - convert-source-map "^2.0.0" - fs-readdir-recursive "^1.1.0" - glob "^7.2.0" - make-dir "^2.1.0" - slash "^2.0.0" - optionalDependencies: - "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.6.0" +"@andrewbranch/untar.js@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" + integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== + +"@arethetypeswrong/cli@^0.17.4": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.4.tgz#45405f75081710c1cf0dab6edb4320fd75d215c8" + integrity sha512-AeiKxtf67XD/NdOqXgBOE5TZWH3EOCt+0GkbUpekOzngc+Q/cRZ5azjWyMxISxxfp0EItgm5NoSld9p7BAA5xQ== + dependencies: + "@arethetypeswrong/core" "0.17.4" + chalk "^4.1.2" + cli-table3 "^0.6.3" + commander "^10.0.1" + marked "^9.1.2" + marked-terminal "^7.1.0" + semver "^7.5.4" -"@babel/code-frame@^7.26.2": +"@arethetypeswrong/core@0.17.4": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.4.tgz#f3f85aa8bbcca6d215938580165e47b2244c7f4c" + integrity sha512-Izvir8iIoU+X4SKtDAa5kpb+9cpifclzsbA8x/AZY0k0gIfXYQ1fa1B6Epfe6vNA2YfDX8VtrZFgvnXB6aPEoQ== + dependencies: + "@andrewbranch/untar.js" "^1.0.3" + "@loaderkit/resolve" "^1.0.2" + cjs-module-lexer "^1.2.3" + fflate "^0.8.2" + lru-cache "^10.4.3" + semver "^7.5.4" + typescript "5.6.1-rc" + validate-npm-package-name "^5.0.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -61,15 +77,6 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/eslint-parser@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.26.10.tgz#4423cb3f84c26978439feabfe23c5aa929400737" - integrity sha512-QsfQZr4AiLpKqn7fz+j7SN+f43z2DZCgGyYbNJ2vJOqKfG4E6MZer1+jqGZqKJaxq/gdO2DC/nUu45+pOL5p2Q== - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.1" - "@babel/generator@^7.26.10": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" @@ -140,7 +147,7 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.25.9": +"@babel/helper-module-imports@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== @@ -819,6 +826,11 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@braidai/lang@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@braidai/lang/-/lang-1.1.0.tgz#1ac39d5b3188e19c963715ba73d7764043367043" + integrity sha512-xyJYkiyNQtTyCLeHxZmOs7rnB94D+N1IjKNArQIh8+8lTBOY7TFgwEV+Ow5a1uaBi5j2w9fLbWcJFTWLDItl5g== + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -829,31 +841,31 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.5.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz#b0fc7e06d0c94f801537fd4237edc2706d3b8e4c" integrity sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.12.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f" + integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ== dependencies: "@eslint/object-schema" "^2.1.6" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/config-helpers@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.1.0.tgz#62f1b7821e9d9ced1b3f512c7ea731825765d1cc" - integrity sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA== +"@eslint/config-helpers@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.1.tgz#26042c028d1beee5ce2235a7929b91c52651646d" + integrity sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw== "@eslint/core@^0.12.0": version "0.12.0" @@ -862,10 +874,10 @@ dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846" - integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ== +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -877,10 +889,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.22.0": - version "9.22.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.22.0.tgz#4ff53649ded7cbce90b444b494c234137fa1aa3d" - integrity sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ== +"@eslint/js@9.24.0", "@eslint/js@^9.24.0": + version "9.24.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.24.0.tgz#685277980bb7bf84ecc8e4e133ccdda7545a691e" + integrity sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA== "@eslint/object-schema@^2.1.6": version "2.1.6" @@ -951,6 +963,13 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.8" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" @@ -1016,17 +1035,33 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== -"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": - version "2.1.8-no-fsevents.3" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" - integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== +"@loaderkit/resolve@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@loaderkit/resolve/-/resolve-1.0.4.tgz#5ba1c2f4cc879d3fb2f066b71b8dc41a88bfb8e9" + integrity sha512-rJzYKVcV4dxJv+vW6jlvagF8zvGxHJ2+HTr1e2qOejfmGhAApgJHl8Aog4mMszxceTRiKTTbnpgmTO1bEZHV/A== + dependencies: + "@braidai/lang" "^1.0.0" -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - eslint-scope "5.1.1" + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -1128,11 +1163,26 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz#05f25dbc9981bee1ae6e713daab10397044a46ca" integrity sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@socket.io/component-emitter@~3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@tsd/typescript@~5.8.3": + version "5.8.3" + resolved "https://registry.yarnpkg.com/@tsd/typescript/-/typescript-5.8.3.tgz#ec20784da0e6c6dcbb2ec585f81cef4a02831840" + integrity sha512-oKarNCN1QUhG148M88mtZdOlBZWWGcInquef+U8QL7gwJkRuNo5WS45Fjsd+3hM9cDJWGpqSZ4Oo097KDx4IWA== + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1186,6 +1236,14 @@ "@types/estree" "*" "@types/json-schema" "*" +"@types/eslint@^7.2.13": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + "@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" @@ -1253,6 +1311,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/minimist@^1.2.0": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -1267,6 +1330,11 @@ dependencies: undici-types "~6.20.0" +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/qs@*": version "6.9.18" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" @@ -1320,6 +1388,87 @@ dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz#593639d9bb5239b2d877d65757b7e2c9100a2e84" + integrity sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.29.1" + "@typescript-eslint/type-utils" "8.29.1" + "@typescript-eslint/utils" "8.29.1" + "@typescript-eslint/visitor-keys" "8.29.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.29.1.tgz#10bf37411be0a199c27b6515726e22fe8d3df8d0" + integrity sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg== + dependencies: + "@typescript-eslint/scope-manager" "8.29.1" + "@typescript-eslint/types" "8.29.1" + "@typescript-eslint/typescript-estree" "8.29.1" + "@typescript-eslint/visitor-keys" "8.29.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz#cfdfd4144f20c38b9d3e430efd6480e297ef52f6" + integrity sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA== + dependencies: + "@typescript-eslint/types" "8.29.1" + "@typescript-eslint/visitor-keys" "8.29.1" + +"@typescript-eslint/type-utils@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz#653dfff5c1711bc920a6a46a5a2c274899f00179" + integrity sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw== + dependencies: + "@typescript-eslint/typescript-estree" "8.29.1" + "@typescript-eslint/utils" "8.29.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.29.1.tgz#984ed1283fedbfb41d3993a9abdcb7b299971500" + integrity sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ== + +"@typescript-eslint/typescript-estree@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz#4ac085665ed5390d11c0e3426427978570e3b747" + integrity sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g== + dependencies: + "@typescript-eslint/types" "8.29.1" + "@typescript-eslint/visitor-keys" "8.29.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.29.1.tgz#3d206c8c8def3527a8eb0588e94e3e60f7e167c9" + integrity sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.29.1" + "@typescript-eslint/types" "8.29.1" + "@typescript-eslint/typescript-estree" "8.29.1" + +"@typescript-eslint/visitor-keys@8.29.1": + version "8.29.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz#9b74e5098c71138d42bbf2178fbe4dfad45d6b9a" + integrity sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg== + dependencies: + "@typescript-eslint/types" "8.29.1" + eslint-visitor-keys "^4.2.0" + "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" @@ -1516,6 +1665,20 @@ ansi-colors@^4.1.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" @@ -1531,7 +1694,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: +ansi-regex@^6.0.1, ansi-regex@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== @@ -1550,11 +1713,21 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1592,6 +1765,16 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -1771,6 +1954,15 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -1808,7 +2000,7 @@ chalk@^2.0.1, chalk@^2.1.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1816,6 +2008,16 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + check-error@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" @@ -1843,11 +2045,37 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== +cjs-module-lexer@^1.2.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-table3@^0.6.3, cli-table3@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -1913,16 +2141,16 @@ colorette@^2.0.10: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - common-path-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" @@ -2077,7 +2305,15 @@ debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: dependencies: ms "^2.1.3" -decamelize@^1.2.0: +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -2149,11 +2385,23 @@ di@^0.0.1: resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dns-packet@^5.2.2: version "5.6.1" resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" @@ -2205,6 +2453,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2253,6 +2506,18 @@ ent@~2.2.0: punycode "^1.4.1" safe-regex-test "^1.1.0" +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" @@ -2300,6 +2565,25 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-formatter-pretty@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz#7a6877c14ffe2672066c853587d89603e97c7708" + integrity sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ== + dependencies: + "@types/eslint" "^7.2.13" + ansi-escapes "^4.2.1" + chalk "^4.1.0" + eslint-rule-docs "^1.1.5" + log-symbols "^4.0.0" + plur "^4.0.0" + string-width "^4.2.0" + supports-hyperlinks "^2.0.0" + +eslint-rule-docs@^1.1.5: + version "1.1.235" + resolved "https://registry.yarnpkg.com/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz#be6ef1fc3525f17b3c859ae2997fedadc89bfb9b" + integrity sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A== + eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -2316,11 +2600,6 @@ eslint-scope@^8.3.0: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" @@ -2331,18 +2610,18 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.22.0: - version "9.22.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.22.0.tgz#0760043809fbf836f582140345233984d613c552" - integrity sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ== +eslint@^9.24.0: + version "9.24.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.24.0.tgz#9a7f2e6cb2de81c405ab244b02f4584c79dc6bee" + integrity sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.2" - "@eslint/config-helpers" "^0.1.0" + "@eslint/config-array" "^0.20.0" + "@eslint/config-helpers" "^0.2.0" "@eslint/core" "^0.12.0" - "@eslint/eslintrc" "^3.3.0" - "@eslint/js" "9.22.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.24.0" "@eslint/plugin-kit" "^0.2.7" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -2410,11 +2689,6 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" - integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== - esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2482,6 +2756,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.2.9, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2497,6 +2782,13 @@ fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" @@ -2504,6 +2796,11 @@ faye-websocket@^0.11.3: dependencies: websocket-driver ">=0.5.1" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" @@ -2664,11 +2961,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-readdir-recursive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" - integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2728,6 +3020,13 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -2735,13 +3034,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -2759,7 +3051,7 @@ glob@^10.4.5: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0: +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -2786,6 +3078,18 @@ globals@^16.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-16.0.0.tgz#3d7684652c5c4fbd086ec82f9448214da49382d8" integrity sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A== +globby@^11.0.1: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -2796,11 +3100,21 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2843,6 +3157,23 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -2921,7 +3252,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -2972,6 +3303,16 @@ ipaddr.js@^2.1.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== +irregular-plurals@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.5.0.tgz#0835e6639aa8425bdc8b0d33d0dc4e89d9c01d2b" + integrity sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -2979,7 +3320,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.16.0: +is-core-module@^2.16.0, is-core-module@^2.5.0: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -3025,6 +3366,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" @@ -3169,6 +3515,21 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jest-diff@^29.0.3: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -3213,7 +3574,7 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.1: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -3314,7 +3675,7 @@ keyv@^4.5.4: dependencies: json-buffer "3.0.1" -kind-of@^6.0.2: +kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -3335,6 +3696,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -3396,7 +3762,7 @@ log-symbols@^2.1.0: dependencies: chalk "^2.0.1" -log-symbols@^4.1.0: +log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -3422,7 +3788,7 @@ loupe@^2.3.6: dependencies: get-func-name "^2.0.1" -lru-cache@^10.2.0: +lru-cache@^10.2.0, lru-cache@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -3434,6 +3800,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -3456,6 +3829,34 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +marked-terminal@^7.1.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.3.0.tgz#7a86236565f3dd530f465ffce9c3f8b62ef270e8" + integrity sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw== + dependencies: + ansi-escapes "^7.0.0" + ansi-regex "^6.1.0" + chalk "^5.4.1" + cli-highlight "^2.1.11" + cli-table3 "^0.6.5" + node-emoji "^2.2.0" + supports-hyperlinks "^3.1.0" + +marked@^9.1.2: + version "9.1.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" + integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== + math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" @@ -3476,6 +3877,24 @@ memfs@^4.6.0: tree-dump "^1.0.1" tslib "^2.0.0" +meow@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" + integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize "^1.2.0" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + merge-descriptors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" @@ -3486,12 +3905,17 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2: +micromatch@^4.0.2, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -3526,6 +3950,11 @@ mime@^2.5.2: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3552,6 +3981,15 @@ minimatch@^9.0.3, minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -3613,6 +4051,15 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3633,6 +4080,16 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-emoji@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.2.0.tgz#1d000e3c76e462577895be1b436f4aa2d6760eb0" + integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw== + dependencies: + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" + node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -3650,6 +4107,26 @@ node-releases@^2.0.19: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -3688,7 +4165,7 @@ nyc@^17.1.0: test-exclude "^6.0.0" yargs "^15.0.2" -object-assign@^4: +object-assign@^4, object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -3843,6 +4320,33 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -3891,6 +4395,11 @@ path-to-regexp@0.1.12: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -3937,11 +4446,27 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +plur@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" + integrity sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg== + dependencies: + irregular-plurals "^3.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -3984,6 +4509,16 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4006,6 +4541,30 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + readable-stream@^2.0.1: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -4035,6 +4594,14 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" @@ -4120,7 +4687,7 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.14.2: +resolve@^1.10.0, resolve@^1.14.2: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -4134,6 +4701,11 @@ retry@^0.13.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + rfdc@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" @@ -4146,21 +4718,6 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup-plugin-babel@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz#d15bd259466a9d1accbdb2fe2fff17c52d030acb" - integrity sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - rollup-pluginutils "^2.8.1" - -rollup-pluginutils@^2.8.1: - version "2.8.2" - resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" - integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== - dependencies: - estree-walker "^0.6.1" - rollup@^4.34.8: version "4.35.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.35.0.tgz#76c95dba17a579df4c00c3955aed32aa5d4dc66d" @@ -4194,6 +4751,13 @@ run-applescript@^7.0.0: resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -4241,7 +4805,7 @@ selfsigned@^2.4.1: "@types/node-forge" "^1.3.0" node-forge "^1" -semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -4251,7 +4815,7 @@ semver@^6.0.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4: +semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== @@ -4394,10 +4958,17 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -slash@^2.0.0: +skin-tone@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== socket.io-adapter@~2.5.2: version "2.5.5" @@ -4462,6 +5033,32 @@ spawn-wrap@^2.0.0: signal-exit "^3.0.2" which "^2.0.1" +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.21" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz#6d6e980c9df2b6fc905343a3b2d702a6239536c3" + integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== + spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -4583,6 +5180,13 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -4595,7 +5199,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -4609,6 +5213,22 @@ supports-color@^8.0.0, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-hyperlinks@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz#b8e485b179681dea496a1e7abdf8985bd3145461" + integrity sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -4649,6 +5269,20 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + thingies@^1.20.0: version "1.21.0" resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" @@ -4681,6 +5315,29 @@ tree-dump@^1.0.1: resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + +tsd@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.32.0.tgz#3e15b0f2ecf880cafe4de1cfaa52980cf767d743" + integrity sha512-R5lBZCbxGBowOcW0gpQaiIjGYrG5NmU+PfFDKcc3zbtzWjML1o/zAwzdDnS2ZheSlPu9GW51azpFqEPUBq9DoQ== + dependencies: + "@tsd/typescript" "~5.8.3" + eslint-formatter-pretty "^4.1.0" + globby "^11.0.1" + jest-diff "^29.0.3" + meow "^9.0.0" + path-exists "^4.0.0" + read-pkg-up "^7.0.0" + tslib@^2.0.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -4698,7 +5355,22 @@ type-detect@^4.0.0, type-detect@^4.1.0: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== -type-fest@^0.8.0: +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -4718,6 +5390,25 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript-eslint@^8.29.1: + version "8.29.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.29.1.tgz#c0b205e542ade22f9027caaaa9c4ec31a202010f" + integrity sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w== + dependencies: + "@typescript-eslint/eslint-plugin" "8.29.1" + "@typescript-eslint/parser" "8.29.1" + "@typescript-eslint/utils" "8.29.1" + +typescript@5.6.1-rc: + version "5.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" + integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== + +typescript@^5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + ua-parser-js@^0.7.30: version "0.7.40" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.40.tgz#c87d83b7bb25822ecfa6397a0da5903934ea1562" @@ -4738,6 +5429,11 @@ unicode-canonical-property-names-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + unicode-match-property-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" @@ -4796,6 +5492,19 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -5019,6 +5728,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" @@ -5027,7 +5741,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -5064,7 +5778,7 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.1.1: +yargs@^16.0.0, yargs@^16.1.1: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==