Files
bun.sh/test/js/bun/patch/patch.test.ts
190n de4182f305 chore: upgrade zig to 0.14.0 (#17820)
Co-authored-by: 190n <7763597+190n@users.noreply.github.com>
Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com>
Co-authored-by: pfg <pfg@pfg.pw>
Co-authored-by: pfgithub <6010774+pfgithub@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-03-14 22:13:31 -07:00

911 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { $ } from "bun";
import { patchInternals } from "bun:internal-for-testing";
import { describe, expect, test } from "bun:test";
import fs from "fs/promises";
import { tempDirWithFiles as __tempDirWithFiles } from "harness";
import { join as __join } from "node:path";
const { parse, apply, makeDiff } = patchInternals;
const makeDiffJs = async (aFolder: string, bFolder: string, cwd: string): Promise<string> => {
const { stdout, stderr } =
await $`git -c core.safecrlf=false diff --src-prefix=a/ --dst-prefix=b/ --ignore-cr-at-eol --irreversible-delete --full-index --no-index ${aFolder} ${bFolder}`
.env(
// https://github.com/pnpm/pnpm/blob/45f4262f0369cadf41cea3b823e8932eae157c4b/patching/plugin-commands-patching/src/patchCommit.ts#L117
{
...process.env,
// #region Predictable output
// These variables aim to ignore the global git config so we get predictable output
// https://git-scm.com/docs/git#Documentation/git.txt-codeGITCONFIGNOSYSTEMcode
GIT_CONFIG_NOSYSTEM: "1",
HOME: "",
XDG_CONFIG_HOME: "",
USERPROFILE: "",
},
)
.quiet()
.cwd(cwd)
// For some reason git diff returns exit code 1 when it is not an error
// So we must check that there is no stderr output instead of the exit code
// to determine if the command was successful
.throws(false);
if (stderr.length > 0) throw new Error(stderr.toString());
const patch = stdout.toString();
return patch
.replace(new RegExp(`(a|b)(${escapeStringRegexp(`/${removeTrailingAndLeadingSlash(aFolder)}/`)})`, "g"), "$1/")
.replace(new RegExp(`(a|b)${escapeStringRegexp(`/${removeTrailingAndLeadingSlash(bFolder)}/`)}`, "g"), "$1/")
.replace(new RegExp(escapeStringRegexp(`${aFolder}/`), "g"), "")
.replace(new RegExp(escapeStringRegexp(`${bFolder}/`), "g"), "");
// .replace(/\n\\ No newline at end of file\n$/, "\n");
};
const tempDirWithFiles: typeof __tempDirWithFiles =
process.platform === "win32"
? (a, b) => __tempDirWithFiles(a.replaceAll("\\", "/"), b).replaceAll("\\", "/")
: __tempDirWithFiles;
const join =
process.platform === "win32"
? (...strings: string[]): string => __join(...strings.map(s => s.replaceAll("\\", "/"))).replaceAll("\\", "/")
: __join;
// Recurse through a nested object, and return a copy where for any object where the only two
// properties are a numeric `capacity` and an array `items`, the capacity has been deleted. Meant to
// be used on serialized Zig structs that contain ArrayLists, because the allocation strategy of
// ArrayList can change and result in a different `capacity` without changing the interpretation of
// the ArrayList's value.
function removeCapacity(patch: any): any {
if (Array.isArray(patch)) {
return patch.map(removeCapacity);
} else if (patch !== null && typeof patch == "object") {
const keys = Object.keys(patch);
keys.sort();
if (keys.length == 2 && keys[0] == "capacity" && keys[1] == "items") {
if (typeof patch.capacity == "number" && Array.isArray(patch.items)) {
// this looks like an ArrayList, so delete the capacity because it is unstable
return { items: patch.items.map(removeCapacity) };
}
}
// ordinary object, so just apply to all the children
const result = {};
for (const k of keys) {
result[k] = removeCapacity(patch[k]);
}
return result;
}
return patch;
}
describe("apply", () => {
test("edgecase", async () => {
const newcontents = "module.exports = x => x % 420 === 0;";
const tempdir2 = tempDirWithFiles("patch-test2", {
".bun/install/cache/is-even@1.0.0": {
"index.js": "module.exports = x => x % 2 === 0;",
},
});
const tempdir = tempDirWithFiles("patch-test", {
a: {},
["node_modules/is-even"]: {
"index.js": newcontents,
},
});
const patchfile = await makeDiff(
`${tempdir2}/.bun/install/cache/is-even@1.0.0`,
`${tempdir}/node_modules/is-even`,
tempdir,
);
await apply(patchfile, `${tempdir}/node_modules/is-even`);
expect(await fs.readFile(`${tempdir}/node_modules/is-even/index.js`).then(b => b.toString())).toBe(newcontents);
});
test("empty", async () => {
const tempdir = tempDirWithFiles("patch-test", {
a: {},
b: {},
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
expect(patchfile).toBe("");
await apply(patchfile, afolder);
expect(await fs.readdir(afolder)).toEqual([]);
});
describe("deletion", () => {
test("simple", async () => {
const files = {
"a/hey.txt": "hello!",
"a/byebye.txt": "goodbye :(",
"b/hey.txt": "hello!",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
console.log("makeDiff args", afolder, bfolder);
const patchfile = await makeDiff(afolder, bfolder, tempdir);
console.log("PATCHFILE", patchfile);
console.log("afolder", afolder);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hey.txt")}`.cwd(tempdir).text()).toBe(files["b/hey.txt"]);
expect(
await $`if ls -d ${join(afolder, "byebye.txt")}; then echo oops; else echo okay!; fi;`.cwd(tempdir).text(),
).toBe("okay!\n");
});
});
describe("creation", () => {
test("simple", async () => {
const files = {
"a": {},
"b/newfile.txt": "hey im new here!",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "newfile.txt")}`.cwd(tempdir).text()).toBe(files["b/newfile.txt"]);
});
test("multi-line", async () => {
const files = {
"a": {},
"b/newfile.txt": "hey im new here!\nhello",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "newfile.txt")}`.cwd(tempdir).text()).toBe(files["b/newfile.txt"]);
});
});
describe("rename", () => {
test("files", async () => {
const files = {
"a/hey.txt": "hello!",
"b/heynow.txt": "hello!",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "heynow.txt")}`.cwd(tempdir).text()).toBe(files["b/heynow.txt"]);
expect(
await $`if ls -d ${join(afolder, "hey.txt")}; then echo oops; else echo okay!; fi;`.cwd(tempdir).text(),
).toBe("okay!\n");
});
test("folders", async () => {
const files = {
"a/foo/hey.txt": "hello!",
"a/foo/hi.txt": "hello!",
"a/foo/lmao.txt": "lmao!",
"b/foo": {},
"b/bar/hey.txt": "hello!",
"b/bar/hi.txt": "hello!",
"b/bar/lmao.txt": "lmao!",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
// Should we remove the folder if it's empty? Technically running `git apply` does this
// But git does not track empty directories so it's not really a problem
// expect(
// await $`if ls -d ${join(afolder, "foo")}; then echo should not exist!; else echo okay!; fi;`
// .cwd(tempdir)
// .text(),
// ).toBe("okay!\n");
expect(await $`cat ${join(afolder, "bar", "hey.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/hey.txt"]);
expect(await $`cat ${join(afolder, "bar", "hi.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/hi.txt"]);
expect(await $`cat ${join(afolder, "bar", "lmao.txt")}`.cwd(tempdir).text()).toBe(files["b/bar/lmao.txt"]);
expect(
await $`ls ${join(afolder, "bar")}`
.cwd(tempdir)
.text()
.then((out: string) =>
out
.split("\n")
.filter(x => x !== "")
.sort(),
),
).toEqual(["hey.txt", "hi.txt", "lmao.txt"].sort());
});
});
describe("mode change", () => {
// chmod doesn't do anything on windows so skiip
test.if(process.platform !== "win32")("simple", async () => {
const files = {
"a/hi.txt": "hello!",
"b/hi.txt": "hi!",
};
const tempdir = tempDirWithFiles("patch-test", files);
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
await fs.chmod(join(bfolder, "hi.txt"), 0o755);
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hi.txt")}`.cwd(tempdir).text()).toBe(files["b/hi.txt"]);
const stat = await fs.stat(join(afolder, "hi.txt"));
expect((stat.mode & parseInt("777", 8)).toString(8)).toBe("755");
});
});
describe("patch", () => {
test("simple insertion", async () => {
const afile = `hello!\n`;
const bfile = `hello!\nwassup?\n`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
test("simple deletion", async () => {
const afile = `hello!\nwassup?\n`;
const bfile = `hello!\n`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
test("multi insertion", async () => {
const afile = `hello!\n`;
const bfile = `lol\nhello!\nwassup?\n`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
test("multi deletion", async () => {
const afile = `hello!\nwassup?\nlmao\n`;
const bfile = `wassup?\n`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
test("multi-hunk insertion", async () => {
const afile = `0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20`;
const bfile = `0
0.5 hi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
19.5 lol hi
20`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
test("multi-hunk deletion", async () => {
const bfile = `0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20`;
const afile = `0
0.5 hi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
19.5 lol hi
20`;
const tempdir = tempDirWithFiles("patch-test", {
"a/hello.txt": afile,
"b/hello.txt": bfile,
});
const afolder = join(tempdir, "a");
const bfolder = join(tempdir, "b");
const patchfile = await makeDiff(afolder, bfolder, tempdir);
await apply(patchfile, afolder);
expect(await $`cat ${join(afolder, "hello.txt")}`.cwd(tempdir).text()).toBe(bfile);
});
});
describe("No newline at end of file", () => {
// TODO: simple, multiline, multiple hunks
});
});
describe("parse", () => {
test("works for a simple case", () => {
expect(removeCapacity(JSON.parse(parse(patch)))).toEqual({
"parts": {
"items": [
{
"file_patch": {
"path": "banana.ts",
"hunks": {
"items": [
{
"header": { "original": { "start": 1, "len": 5 }, "patched": { "start": 1, "len": 5 } },
"parts": {
"items": [
{
"type": "context",
"lines": { "items": ["this", "is", ""] },
"no_newline_at_end_of_file": false,
},
{
"type": "deletion",
"lines": { "items": ["a"] },
"no_newline_at_end_of_file": false,
},
{
"type": "insertion",
"lines": { "items": [""] },
"no_newline_at_end_of_file": false,
},
{
"type": "context",
"lines": { "items": ["file"] },
"no_newline_at_end_of_file": false,
},
],
},
},
],
},
"before_hash": "2de83dd",
"after_hash": "842652c",
},
},
],
},
});
});
test("fails when the patch file has invalid headers", () => {
expect(() => parse(invalidHeaders1)).toThrow();
expect(() => parse(invalidHeaders2)).toThrow();
expect(() => parse(invalidHeaders3)).toThrow();
expect(() => parse(invalidHeaders4)).toThrow();
expect(() => parse(invalidHeaders5)).toThrow();
});
test("is OK when blank lines are accidentally created", () => {
expect(parse(accidentalBlankLine)).toEqual(parse(patch));
});
test(`can handle files with CRLF line breaks`, () => {
expect(removeCapacity(JSON.parse(parse(crlfLineBreaks)))).toEqual({
"parts": {
"items": [
{
"file_creation": {
"path": "banana.ts",
"mode": "non_executable",
"hunk": {
"header": { "original": { "start": 1, "len": 0 }, "patched": { "start": 1, "len": 1 } },
"parts": {
"items": [
{
"type": "insertion",
"lines": { "items": ["this is a new file\r"] },
"no_newline_at_end_of_file": false,
},
],
},
},
"hash": "3e1267f",
},
},
],
},
});
});
test("works", () => {
expect(removeCapacity(JSON.parse(parse(modeChangeAndModifyAndRename)))).toEqual({
"parts": {
"items": [
{ "file_rename": { "from_path": "numbers.txt", "to_path": "banana.txt" } },
{ "file_mode_change": { "path": "banana.txt", "old_mode": "non_executable", "new_mode": "executable" } },
{
"file_patch": {
"path": "banana.txt",
"hunks": {
"items": [
{
"header": { "original": { "start": 1, "len": 4 }, "patched": { "start": 1, "len": 4 } },
"parts": {
"items": [
{
"type": "deletion",
"lines": { "items": ["one"] },
"no_newline_at_end_of_file": false,
},
{
"type": "insertion",
"lines": { "items": ["ne"] },
"no_newline_at_end_of_file": false,
},
{
"type": "context",
"lines": { "items": ["", "two", ""] },
"no_newline_at_end_of_file": false,
},
],
},
},
],
},
"before_hash": "fbf1785",
"after_hash": "92d2c5f",
},
},
],
},
});
});
test("parses old-style patches", () => {
expect(removeCapacity(JSON.parse(parse(oldStylePatch)))).toEqual({
"parts": {
"items": [
{
"file_patch": {
"path": "node_modules/graphql/utilities/assertValidName.js",
"hunks": {
"items": [
{
"header": { "original": { "start": 41, "len": 10 }, "patched": { "start": 41, "len": 11 } },
"parts": {
"items": [
{
"type": "context",
"lines": {
"items": [
" */",
"function isValidNameError(name, node) {",
" !(typeof name === 'string') ? (0, _invariant2.default)(0, 'Expected string') : void 0;",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "deletion",
"lines": {
"items": [
" if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
" return new _GraphQLError.GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
" }",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "insertion",
"lines": {
"items": [
" // if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
" // return new _GraphQLError.GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
" // }",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "context",
"lines": {
"items": [
" if (!NAME_RX.test(name)) {",
" return new _GraphQLError.GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"' + name + '\" does not.', node);",
" }",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "insertion",
"lines": { "items": [""] },
"no_newline_at_end_of_file": false,
},
{
"type": "context",
"lines": { "items": ["}"] },
"no_newline_at_end_of_file": true,
},
],
},
},
],
},
"before_hash": null,
"after_hash": null,
},
},
{
"file_patch": {
"path": "node_modules/graphql/utilities/assertValidName.mjs",
"hunks": {
"items": [
{
"header": { "original": { "start": 29, "len": 9 }, "patched": { "start": 29, "len": 9 } },
"parts": {
"items": [
{
"type": "context",
"lines": {
"items": [
" */",
"export function isValidNameError(name, node) {",
" !(typeof name === 'string') ? invariant(0, 'Expected string') : void 0;",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "deletion",
"lines": {
"items": [
" if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
" return new GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
" }",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "insertion",
"lines": {
"items": [
" // if (name.length > 1 && name[0] === '_' && name[1] === '_') {",
" // return new GraphQLError('Name \"' + name + '\" must not begin with \"__\", which is reserved by ' + 'GraphQL introspection.', node);",
" // }",
],
},
"no_newline_at_end_of_file": false,
},
{
"type": "context",
"lines": {
"items": [
" if (!NAME_RX.test(name)) {",
" return new GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"' + name + '\" does not.', node);",
" }",
],
},
"no_newline_at_end_of_file": false,
},
],
},
},
],
},
"before_hash": null,
"after_hash": null,
},
},
],
},
});
});
});
const patch = `diff --git a/banana.ts b/banana.ts\nindex 2de83dd..842652c 100644\n--- a/banana.ts\n+++ b/banana.ts\n@@ -1,5 +1,5 @@\n this\n is\n \n-a\n+\n file\n`;
const invalidHeaders1 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,4 @@
this
is
-a
+
file
`;
const invalidHeaders2 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,4 +1,5 @@
this
is
-a
+
file
`;
const invalidHeaders3 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,0 +1,5 @@
this
is
-a
+
file
`;
const invalidHeaders4 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,0 @@
this
is
-a
+
file
`;
const invalidHeaders5 = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,5@@
this
is
-a
+
file
`;
const accidentalBlankLine = /* diff */ `diff --git a/banana.ts b/banana.ts
index 2de83dd..842652c 100644
--- a/banana.ts
+++ b/banana.ts
@@ -1,5 +1,5 @@
this
is
-a
+
file
`;
const crlfLineBreaks = /* diff */ `diff --git a/banana.ts b/banana.ts
new file mode 100644
index 0000000..3e1267f
--- /dev/null
+++ b/banana.ts
@@ -0,0 +1 @@
+this is a new file
`.replace(/\n/g, "\r\n");
const modeChangeAndModifyAndRename = /* diff */ `diff --git a/numbers.txt b/banana.txt
old mode 100644
new mode 100755
similarity index 96%
rename from numbers.txt
rename to banana.txt
index fbf1785..92d2c5f
--- a/numbers.txt
+++ b/banana.txt
@@ -1,4 +1,4 @@
-one
+ne
two
`;
const oldStylePatch = /* diff */ `patch-package
--- a/node_modules/graphql/utilities/assertValidName.js
+++ b/node_modules/graphql/utilities/assertValidName.js
@@ -41,10 +41,11 @@ function assertValidName(name) {
*/
function isValidNameError(name, node) {
!(typeof name === 'string') ? (0, _invariant2.default)(0, 'Expected string') : void 0;
- if (name.length > 1 && name[0] === '_' && name[1] === '_') {
- return new _GraphQLError.GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
- }
+ // if (name.length > 1 && name[0] === '_' && name[1] === '_') {
+ // return new _GraphQLError.GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
+ // }
if (!NAME_RX.test(name)) {
return new _GraphQLError.GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "' + name + '" does not.', node);
}
+
}
\\ No newline at end of file
--- a/node_modules/graphql/utilities/assertValidName.mjs
+++ b/node_modules/graphql/utilities/assertValidName.mjs
@@ -29,9 +29,9 @@ export function assertValidName(name) {
*/
export function isValidNameError(name, node) {
!(typeof name === 'string') ? invariant(0, 'Expected string') : void 0;
- if (name.length > 1 && name[0] === '_' && name[1] === '_') {
- return new GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
- }
+ // if (name.length > 1 && name[0] === '_' && name[1] === '_') {
+ // return new GraphQLError('Name "' + name + '" must not begin with "__", which is reserved by ' + 'GraphQL introspection.', node);
+ // }
if (!NAME_RX.test(name)) {
return new GraphQLError('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "' + name + '" does not.', node);
}
`;
function escapeStringRegexp(string: string) {
if (typeof string !== "string") {
throw new TypeError("Expected a string");
}
// Escape characters with special meaning either inside or outside character sets.
// Use a simple backslash escape when its always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns stricter grammar.
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
}
function removeTrailingAndLeadingSlash(p: string): string {
if (p[0] === "/" || p.endsWith("/")) {
return p.replace(/^\/|\/$/g, "");
}
return p;
}