diff --git a/.gitattributes b/.gitattributes index d09fa586c..e07750037 100644 --- a/.gitattributes +++ b/.gitattributes @@ -44,3 +44,6 @@ refactor/baselines/site-vendor/monaco-core-build/** -text whitespace=-trailing-s refactor/baselines/monaco-core-locales.json text eol=lf refactor/baselines/site-vendor/monaco-contributions/** -text whitespace=-trailing-space,cr-at-eol refactor/baselines/site-vendor/monaco-css-build/** -text whitespace=-trailing-space,cr-at-eol +refactor/baselines/site-vendor/monaco-node-path/** -text whitespace=-trailing-space,cr-at-eol +docs/licenses/monaco-editor/core-path/** -text whitespace=-trailing-space,cr-at-eol +refactor/baselines/monaco-node-path-fixtures.json text eol=lf diff --git a/docs/THIRD_PARTY_NOTICES.md b/docs/THIRD_PARTY_NOTICES.md index a3915460d..ea022e745 100644 --- a/docs/THIRD_PARTY_NOTICES.md +++ b/docs/THIRD_PARTY_NOTICES.md @@ -117,6 +117,10 @@ Included component: marked (Monaco core) 3.0.2 Source: https://registry.npmjs.org/marked/-/marked-3.0.2.tgz +Included component: nodejs path (Monaco core) 14.16.0 + +Source: https://github.com/nodejs/node/blob/bd60e93357a118204ea238d94e7a9e4209d93062/lib/path.js + - licenses/monaco-editor/LICENSE - licenses/monaco-editor/ThirdPartyNotices.txt - licenses/monaco-editor/codicons/LICENSE @@ -143,6 +147,7 @@ Source: https://registry.npmjs.org/marked/-/marked-3.0.2.tgz - licenses/monaco-editor/core-origins/marked-LICENSE - licenses/monaco-editor/core-origins/marked-vscode-license.txt - licenses/monaco-editor/core-origins/ATTRIBUTION.md +- licenses/monaco-editor/core-path/ATTRIBUTION.md ## console legacy-vendor-with-TS-adapter diff --git a/docs/licenses/monaco-editor/core-path/ATTRIBUTION.md b/docs/licenses/monaco-editor/core-path/ATTRIBUTION.md new file mode 100644 index 000000000..9b93f7595 --- /dev/null +++ b/docs/licenses/monaco-editor/core-path/ATTRIBUTION.md @@ -0,0 +1,22 @@ +# Node path in Monaco 0.30.1 + +The editor and core worker include VS Code's browser port of Node.js path. The +source identifies Node v14.16.0 ([fixed Node source](https://github.com/nodejs/node/blob/bd60e93357a118204ea238d94e7a9e4209d93062/lib/path.js)), +ported in [VS Code 829382514cb1065f5ebb90f436e1c6103e153953](https://github.com/microsoft/vscode/blob/829382514cb1065f5ebb90f436e1c6103e153953/src/vs/base/common/path.ts). + +The full Joyent/Node contributor copyright and permission text is already in +[the original third-party notices](../ThirdPartyNotices.txt). That upstream index +links an older Node commit; this supplement identifies the actual ported source +without removing or rewriting the original notice. Its license text matches both +the v14.16.0 source and VS Code's retained comment. + +VS Code adds types and a browser process adapter, local argument validation and +an Error subclass, exports platform-selected functions, and omits Node's legacy +_makeLong alias. Reviewed algorithm edits include a string guard in Windows join +and shared loop-variable declarations in both basename implementations. These +are existing upstream adaptations, not new ArtPlayer behavior changes. + +Six unused top-level exports are removed from the prepared sources. Both actual +source maps retain identical complete path-module sources after those removals; +TypeScript 4.5.0-dev.20211021 reproduces the full module in editor and worker builds. +The full VS Code compiler pipeline is a separate verification scope. diff --git a/package.json b/package.json index 9babf35e2..ea6dd27e6 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,8 @@ "verify:monaco-core-origins": "node scripts/site-vendor/monaco/reproduce-core-origins.ts", "verify:monaco-core-build": "node scripts/site-vendor/monaco/reproduce-core-build.ts", "verify:monaco-contributions": "node scripts/site-vendor/monaco/reproduce-contributions.ts", - "verify:monaco-css": "node scripts/site-vendor/monaco/reproduce-css.ts" + "verify:monaco-css": "node scripts/site-vendor/monaco/reproduce-css.ts", + "verify:monaco-node-path": "node scripts/site-vendor/monaco/reproduce-node-path.ts" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/refactor/baselines/monaco-node-path-fixtures.json b/refactor/baselines/monaco-node-path-fixtures.json new file mode 100644 index 000000000..89df8d864 --- /dev/null +++ b/refactor/baselines/monaco-node-path-fixtures.json @@ -0,0 +1,275 @@ +[ + { + "namespace": "posix", + "method": "normalize", + "args": [ + "/foo//bar/../baz/" + ], + "expected": "/foo/baz/" + }, + { + "namespace": "posix", + "method": "normalize", + "args": [ + "../../a/./b" + ], + "expected": "../../a/b" + }, + { + "namespace": "posix", + "method": "normalize", + "args": [ + "" + ], + "expected": "." + }, + { + "namespace": "posix", + "method": "basename", + "args": [ + "/a/file.txt", + ".txt" + ], + "expected": "file" + }, + { + "namespace": "posix", + "method": "basename", + "args": [ + "/a/.profile" + ], + "expected": ".profile" + }, + { + "namespace": "posix", + "method": "dirname", + "args": [ + "/a/b/" + ], + "expected": "/a" + }, + { + "namespace": "posix", + "method": "extname", + "args": [ + "/a/.hidden" + ], + "expected": "" + }, + { + "namespace": "posix", + "method": "extname", + "args": [ + "/a/file.tar.gz" + ], + "expected": ".gz" + }, + { + "namespace": "posix", + "method": "join", + "args": [ + "", + "a", + "..", + "b" + ], + "expected": "b" + }, + { + "namespace": "posix", + "method": "relative", + "args": [ + "/a/b", + "/a/c/d" + ], + "expected": "../c/d" + }, + { + "namespace": "posix", + "method": "resolve", + "args": [ + "/a", + "../b" + ], + "expected": "/b" + }, + { + "namespace": "posix", + "method": "parse", + "args": [ + "/文件/report.txt" + ], + "expected": { + "root": "/", + "dir": "/文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + } + }, + { + "namespace": "posix", + "method": "format", + "args": [ + { + "root": "/", + "dir": "/a", + "base": "b.txt" + } + ], + "expected": "/a/b.txt" + }, + { + "namespace": "posix", + "method": "isAbsolute", + "args": [ + "../a" + ], + "expected": false + }, + { + "namespace": "posix", + "method": "toNamespacedPath", + "args": [ + "/a/b" + ], + "expected": "/a/b" + }, + { + "namespace": "win32", + "method": "normalize", + "args": [ + "C:\\foo\\..\\bar\\" + ], + "expected": "C:\\bar\\" + }, + { + "namespace": "win32", + "method": "normalize", + "args": [ + "\\\\server\\share\\a\\..\\b" + ], + "expected": "\\\\server\\share\\b" + }, + { + "namespace": "win32", + "method": "normalize", + "args": [ + "" + ], + "expected": "." + }, + { + "namespace": "win32", + "method": "basename", + "args": [ + "C:\\a\\file.txt", + ".txt" + ], + "expected": "file" + }, + { + "namespace": "win32", + "method": "basename", + "args": [ + "C:\\a\\.profile" + ], + "expected": ".profile" + }, + { + "namespace": "win32", + "method": "dirname", + "args": [ + "C:\\a\\b\\" + ], + "expected": "C:\\a" + }, + { + "namespace": "win32", + "method": "extname", + "args": [ + "C:\\a\\file.tar.gz" + ], + "expected": ".gz" + }, + { + "namespace": "win32", + "method": "join", + "args": [ + "\\\\server", + "share", + "a" + ], + "expected": "\\\\server\\share\\a" + }, + { + "namespace": "win32", + "method": "join", + "args": [ + "", + "", + "a" + ], + "expected": "a" + }, + { + "namespace": "win32", + "method": "relative", + "args": [ + "C:\\a\\b", + "C:\\a\\c" + ], + "expected": "..\\c" + }, + { + "namespace": "win32", + "method": "resolve", + "args": [ + "C:\\a", + "..\\b" + ], + "expected": "C:\\b" + }, + { + "namespace": "win32", + "method": "parse", + "args": [ + "C:\\文件\\report.txt" + ], + "expected": { + "root": "C:\\", + "dir": "C:\\文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + } + }, + { + "namespace": "win32", + "method": "format", + "args": [ + { + "root": "C:\\", + "dir": "C:\\a", + "base": "b.txt" + } + ], + "expected": "C:\\a\\b.txt" + }, + { + "namespace": "win32", + "method": "isAbsolute", + "args": [ + "C:a" + ], + "expected": false + }, + { + "namespace": "win32", + "method": "toNamespacedPath", + "args": [ + "C:\\a\\b" + ], + "expected": "\\\\?\\C:\\a\\b" + } +] diff --git a/refactor/baselines/monaco-node-path-provenance.json b/refactor/baselines/monaco-node-path-provenance.json new file mode 100644 index 000000000..eb0f49bb1 --- /dev/null +++ b/refactor/baselines/monaco-node-path-provenance.json @@ -0,0 +1,142 @@ +{ + "schemaVersion": 1, + "task": "SITE-07", + "nodeCommit": "bd60e93357a118204ea238d94e7a9e4209d93062", + "archives": [ + { + "name": "monaco-editor", + "version": "0.30.1", + "tarball": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.30.1.tgz", + "integrity": "sha512-B/y4+b2O5G2gjuxIFtCE2EkM17R2NM7/3F8x0qcPsqy4V83bitJTIO4TIeZpYlzu/xy6INiY/+84BEm6+7Cmzg==", + "sha256": "d7872ab742036036eca48cd54e43d751bede56742f0689bcf4049bcf701468c7" + }, + { + "name": "monaco-editor-core", + "version": "0.30.1", + "tarball": "https://registry.npmjs.org/monaco-editor-core/-/monaco-editor-core-0.30.1.tgz", + "integrity": "sha512-WNxfchYafMZXVfysqg/ESW4MtOpYLPaIKrzudNlgwYvfYID+O/nwSZI5X+KxW84roVzHsNgFGyOOesfHFZwYUA==", + "sha256": "a3b004061373a7f6217eba6399f1a1de31970bee2eee693aada289035198c5ec" + }, + { + "name": "typescript", + "version": "4.5.0-dev.20211021", + "tarball": "https://registry.yarnpkg.com/typescript/-/typescript-4.5.0-dev.20211021.tgz", + "integrity": "sha512-lZJTNSL8CBqgURSrVwCto8WEV1U0BUwoy+GTmINoRc/Sm2BcYiRpcq/OAiSg/1q/rvODousdOsHePfAZfwi0Qg==", + "sha256": "27cbe79009d92bcbcbbb7c5575fd8a0821d16eb26b410c249db018d49b10a693" + } + ], + "remotes": [ + { + "source": "refactor/baselines/site-vendor/monaco-node-path/node-path.js.txt", + "sha256": "ff755282e187b98ebb2f5381d3caf235cee94afb97d7d994007079a01694319e", + "gitBlobSha": "7532b795bf63f7190bb46946d2db69459bce08a7", + "apiUrl": "https://api.github.com/repos/nodejs/node/contents/lib/path.js?ref=bd60e93357a118204ea238d94e7a9e4209d93062" + }, + { + "source": "refactor/baselines/site-vendor/monaco-node-path/vscode-path.ts.txt", + "sha256": "d500777083482e1c9db549b44b0c8de6702fd1bdb4aab8f51ad2a3d670960676", + "gitBlobSha": "00d948acd88597642885d21ea1dac6e6a860949c", + "apiUrl": "https://api.github.com/repos/microsoft/vscode/contents/src/vs/base/common/path.ts?ref=829382514cb1065f5ebb90f436e1c6103e153953" + }, + { + "source": "refactor/baselines/site-vendor/monaco-css-build/yarn.lock.txt", + "sha256": "9f74da55701070e0d2203daf46b1552e12a95c0b7ee9bd10694fa2b61a47227a", + "gitBlobSha": "2b4d4ce38436144cd7b3405a1eef1a4cd7d53cac", + "apiUrl": "https://api.github.com/repos/microsoft/vscode/contents/yarn.lock?ref=829382514cb1065f5ebb90f436e1c6103e153953" + } + ], + "nodeSource": "refactor/baselines/site-vendor/monaco-node-path/node-path.js.txt", + "vscodeSource": "refactor/baselines/site-vendor/monaco-node-path/vscode-path.ts.txt", + "lockSource": "refactor/baselines/site-vendor/monaco-css-build/yarn.lock.txt", + "maps": [ + { + "archive": "monaco-editor-core-0.30.1", + "member": "package/min-maps/vs/editor/editor.main.js.map", + "sha256": "981ef3b9027387b50c8a035dbb14e66ce6ecbc6a3e617db423c585d0f434057f", + "source": "out-editor/vs/editor/file:/Users/alex/src/vscode/out-editor-src/vs/base/common/path.ts" + }, + { + "archive": "monaco-editor-core-0.30.1", + "member": "package/min-maps/vs/base/worker/workerMain.js.map", + "sha256": "824cd08c975e30bd9d8942051e8a38f9f852efb3a6c09984c25d845d0a5e36a5", + "source": "out-editor/vs/base/worker/file:/Users/alex/src/vscode/out-editor-src/vs/base/common/path.ts" + } + ], + "outputs": [ + { + "development": { + "archive": "monaco-editor-core-0.30.1", + "member": "package/dev/vs/editor/editor.main.js", + "sha256": "483348c3a73e86f201bc0df26ceee2c3ef610fe56c344993410fc093bac228a7" + }, + "signature": "define(__m[75/*vs/base/common/path*/], __M([0/*require*/,1/*exports*/,300/*vs/base/common/process*/]),", + "offset": 334819, + "target": { + "path": "docs/assets/js/vs/editor/editor.main.js", + "sha256": "05a50ed50bb013b6e7a82d25686186aa7bdff8b8209c7badab00e046b5e99618" + }, + "editor": { + "archive": "monaco-editor-0.30.1", + "member": "package/min/vs/editor/editor.main.js", + "sha256": "05a50ed50bb013b6e7a82d25686186aa7bdff8b8209c7badab00e046b5e99618" + } + }, + { + "development": { + "archive": "monaco-editor-core-0.30.1", + "member": "package/dev/vs/base/worker/workerMain.js", + "sha256": "016d569eb0ae9ba9865bc9c9be1da9fe88f722622c60828c4fa93b73245e2a42" + }, + "signature": "define(__m[20/*vs/base/common/path*/], __M([0/*require*/,1/*exports*/,19/*vs/base/common/process*/]),", + "offset": 159086, + "target": { + "path": "docs/assets/js/vs/base/worker/workerMain.js", + "sha256": "3ebdb68f45f9fed1c587901c9db42dd70e8a552421e330f39116fc7b078ba67e" + }, + "editor": { + "archive": "monaco-editor-0.30.1", + "member": "package/min/vs/base/worker/workerMain.js", + "sha256": "3ebdb68f45f9fed1c587901c9db42dd70e8a552421e330f39116fc7b078ba67e" + } + } + ], + "component": { + "name": "nodejs path (Monaco core)", + "version": "14.16.0", + "tarball": "https://github.com/nodejs/node/blob/bd60e93357a118204ea238d94e7a9e4209d93062/lib/path.js", + "assets": [ + "docs/assets/js/vs/editor/editor.main.js", + "docs/assets/js/vs/base/worker/workerMain.js" + ], + "notices": [ + "docs/licenses/monaco-editor/ThirdPartyNotices.txt", + "docs/licenses/monaco-editor/core-path/ATTRIBUTION.md" + ] + }, + "legacyNotice": { + "source": "refactor/baselines/site-vendor/monaco-editor-ThirdPartyNotices.txt.txt", + "target": "docs/licenses/monaco-editor/ThirdPartyNotices.txt", + "sha256": "b23ab6e9a1a31ce6948a98b16a83474c999a384ece75643dd0affc83ea1724ef" + }, + "attribution": { + "source": "refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt", + "target": "docs/licenses/monaco-editor/core-path/ATTRIBUTION.md", + "sha256": "40583c701c0bcbd8e10935652a0a8b02e2073b0ef040fb7be7b939c437d8b8dc" + }, + "fixtures": { + "path": "refactor/baselines/monaco-node-path-fixtures.json", + "sha256": "9c3ad261603d231a093e98ed4a43f302c1aa722d696f97c129d9bf95fa083ccc" + }, + "review": { + "classification": "Existing modified browser port; not an unmodified Node runtime", + "algorithms": [ + "Windows join adds a typeof firstPart string guard", + "Both basename implementations share a loop index declared outside the loops" + ], + "environment": [ + "Local process adapter and validation/Error subclass", + "Typed interfaces, explicit exports and platform aliases; Node legacy _makeLong omitted" + ], + "unmodifiedSemanticEquivalenceClaimed": false + } +} diff --git a/refactor/baselines/monaco-node-path-validation.json b/refactor/baselines/monaco-node-path-validation.json new file mode 100644 index 000000000..2e7f5062e --- /dev/null +++ b/refactor/baselines/monaco-node-path-validation.json @@ -0,0 +1,350 @@ +{ + "schemaVersion": 1, + "task": "SITE-07", + "status": "checkpoint-incomplete", + "recordedAt": "2026-09-15T04:12:40.550Z", + "baseCommit": "d2b2f76db84ba107751766935a79b78766b81c7d", + "node": "v24.21.0", + "yarn": "1.22.22", + "fingerprints": { + "refactor/baselines/monaco-node-path-provenance.json": "45872f22f226922a57dde70b9ad2d16c7844c6da0ffd7f6f5d23c1fdcfafa6fd", + "refactor/baselines/monaco-node-path-fixtures.json": "9c3ad261603d231a093e98ed4a43f302c1aa722d696f97c129d9bf95fa083ccc", + "scripts/site-vendor/monaco/node-path.ts": "dbcd863a436af83e9969b99bad600df8fc6d42f460ac5b03e3e9408cb11f4da8", + "scripts/site-vendor/monaco/reproduce-node-path.ts": "6ce26a15ddd3fdcdeb0deb62e5e2ef81aadf00c85c284e1204eeaf2b8d648c4c", + "scripts/site-vendor/manifest.json": "5ed425e4136a7fd59a4784cd52070dc9be21066f3ba4757778ce4da5d8a5ed7b", + "scripts/build-site-notices.mjs": "95576c1f36fbfcc1613813ae7cf101b871d0dd7a201a3edcbe26003d96e4da03", + "test/browser/editor-path.spec.js": "cae3cbfee45494e95924b3eb1d31de92bee9903d0ab381c578c162f2071b551d", + "test/browser/site-vendor.spec.js": "bd9abd585d446a2bbeefee7ed750f2e302cc84573b8a7ce45007d8933dc692bd", + "docs/licenses/monaco-editor/core-path/ATTRIBUTION.md": "40583c701c0bcbd8e10935652a0a8b02e2073b0ef040fb7be7b939c437d8b8dc", + "docs/THIRD_PARTY_NOTICES.md": "048ba3ef9aea250c7683cafc8d0cb7d5b36a853d49b5e8ab406dc2b07c852e2d" + }, + "reproduction": { + "result": { + "archives": 3, + "gitSources": 3, + "maps": 2, + "removedUnusedExports": 6, + "outputs": [ + { + "path": "docs/assets/js/vs/editor/editor.main.js", + "offset": 334819, + "moduleBytes": 57628, + "exactModule": true, + "sha256": "05a50ed50bb013b6e7a82d25686186aa7bdff8b8209c7badab00e046b5e99618" + }, + { + "path": "docs/assets/js/vs/base/worker/workerMain.js", + "offset": 159086, + "moduleBytes": 57627, + "exactModule": true, + "sha256": "3ebdb68f45f9fed1c587901c9db42dd70e8a552421e330f39116fc7b078ba67e" + } + ], + "fixtures": 30, + "existingLicenseTextMatches": true, + "compiler": "4.5.0-dev.20211021", + "fullCoreTypeScriptBuild": false, + "unmodifiedNodeRuntimeClaimed": false + }, + "offline": { + "exitCode": 0, + "logSha256": "5877aa1df3cfcd002b8c360546bbe15974e209317819cb35df5394916582670e" + }, + "online": { + "exitCode": 0, + "logSha256": "5877aa1df3cfcd002b8c360546bbe15974e209317819cb35df5394916582670e" + } + }, + "unit": { + "passed": 28, + "failed": 0, + "logSha256": "548fcbf988efb129c426c013ac2038992f103c274363b7bbcd422b395466dfc2" + }, + "types": { + "exitCode": 0, + "logSha256": "975a28d6b24bd2efde60d6feb9a4731f84df8c04c367cf1d5a1de9453dc84262" + }, + "lint": { + "exitCode": 0 + }, + "notices": { + "files": 86, + "outputs": 87 + }, + "browser": { + "command": "yarn test:browser test/browser/editor-path.spec.js test/browser/site-vendor.spec.js --workers=1", + "reportSha256": "35b5b4cd62c5e5993f3c0398ebc31c5871fc30b6ab1aebbd2ce41967dee06c8b", + "stats": { + "startTime": "2026-09-15T04:07:59.233Z", + "duration": 14093.269, + "expected": 6, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "errors": [], + "tests": [ + { + "title": "docs Monaco path port preserves frozen Node path results in both namespaces", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [], + "path": { + "cases": 30, + "values": [ + "/foo/baz/", + "../../a/b", + ".", + "file", + ".profile", + "/a", + "", + ".gz", + "b", + "../c/d", + "/b", + { + "root": "/", + "dir": "/文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "/a/b.txt", + false, + "/a/b", + "C:\\bar\\", + "\\\\server\\share\\b", + ".", + "file", + ".profile", + "C:\\a", + ".gz", + "\\\\server\\share\\a", + "a", + "..\\c", + "C:\\b", + { + "root": "C:\\", + "dir": "C:\\文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "C:\\a\\b.txt", + false, + "\\\\?\\C:\\a\\b" + ], + "namespace": "win32", + "alias": true, + "invalid": { + "name": "Error", + "code": "ERR_INVALID_ARG_TYPE" + } + } + }, + { + "title": "docs Monaco path port preserves frozen Node path results in both namespaces", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [], + "path": { + "cases": 30, + "values": [ + "/foo/baz/", + "../../a/b", + ".", + "file", + ".profile", + "/a", + "", + ".gz", + "b", + "../c/d", + "/b", + { + "root": "/", + "dir": "/文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "/a/b.txt", + false, + "/a/b", + "C:\\bar\\", + "\\\\server\\share\\b", + ".", + "file", + ".profile", + "C:\\a", + ".gz", + "\\\\server\\share\\a", + "a", + "..\\c", + "C:\\b", + { + "root": "C:\\", + "dir": "C:\\文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "C:\\a\\b.txt", + false, + "\\\\?\\C:\\a\\b" + ], + "namespace": "win32", + "alias": true, + "invalid": { + "name": "Error", + "code": "ERR_INVALID_ARG_TYPE" + } + } + }, + { + "title": "docs Monaco path port preserves frozen Node path results in both namespaces", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [], + "path": { + "cases": 30, + "values": [ + "/foo/baz/", + "../../a/b", + ".", + "file", + ".profile", + "/a", + "", + ".gz", + "b", + "../c/d", + "/b", + { + "root": "/", + "dir": "/文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "/a/b.txt", + false, + "/a/b", + "C:\\bar\\", + "\\\\server\\share\\b", + ".", + "file", + ".profile", + "C:\\a", + ".gz", + "\\\\server\\share\\a", + "a", + "..\\c", + "C:\\b", + { + "root": "C:\\", + "dir": "C:\\文件", + "base": "report.txt", + "ext": ".txt", + "name": "report" + }, + "C:\\a\\b.txt", + false, + "\\\\?\\C:\\a\\b" + ], + "namespace": "posix", + "alias": true, + "invalid": { + "name": "Error", + "code": "ERR_INVALID_ARG_TYPE" + } + } + }, + { + "title": "mobile vConsole shows logs and upstream site notice texts are served unchanged", + "project": "chromium", + "status": "passed", + "browser": "153.0.8010.12", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [ + { + "url": "http://127.0.0.1:8084/test/pattern.mp4", + "resourceType": "media", + "failure": { + "errorText": "net::ERR_ABORTED" + } + } + ], + "notices": { + "count": 86, + "sha256": "fd92aad7f34fc1daf6d700a8ab48f818effbe2d8f85aa9a3c1fa008d3680bdb7" + } + }, + { + "title": "mobile vConsole shows logs and upstream site notice texts are served unchanged", + "project": "firefox", + "status": "passed", + "browser": "155.0", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [], + "notices": { + "count": 86, + "sha256": "fd92aad7f34fc1daf6d700a8ab48f818effbe2d8f85aa9a3c1fa008d3680bdb7" + } + }, + { + "title": "mobile vConsole shows logs and upstream site notice texts are served unchanged", + "project": "webkit", + "status": "passed", + "browser": "26.6", + "platform": "win32", + "errors": [], + "consoleErrors": [], + "failedRequests": [], + "notices": { + "count": 86, + "sha256": "fd92aad7f34fc1daf6d700a8ab48f818effbe2d8f85aa9a3c1fa008d3680bdb7" + } + } + ] + }, + "limitations": [ + "The Node reference harness covers deterministic valid calls, not Node error formatting or cwd/drive behavior.", + "Full original core TypeScript compilation and remaining embedded origins are not closed by one module proof.", + "Chromium mobile playback passed its currentTime assertion but recorded one pattern.mp4 net::ERR_ABORTED request; that observation is retained.", + "Runtime assets are unchanged. No device, external SDK, remote CI or publication evidence is claimed.", + "The CSS publication newline-stage limitation and pending Thumbnail default policy remain recorded separately." + ], + "finalPathCorrection": { + "source": "refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt", + "reason": "Frozen deployed text contains a site-relative link; it is not a repository Markdown document.", + "noticeCheckExitCode": 0, + "planCheckExitCode": 0, + "unit": { + "passed": 28, + "failed": 0, + "logSha256": "7b904dd4144c6e9638c8e269370fae60a91ccf8156eca13391b9c484585d8967" + }, + "offline": { + "exitCode": 0, + "logSha256": "a04f121993cf78bc33a52d07a0b37617a329d5364470908060f742b36ff4b0a4" + } + } +} diff --git a/refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt b/refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt new file mode 100644 index 000000000..9b93f7595 --- /dev/null +++ b/refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt @@ -0,0 +1,22 @@ +# Node path in Monaco 0.30.1 + +The editor and core worker include VS Code's browser port of Node.js path. The +source identifies Node v14.16.0 ([fixed Node source](https://github.com/nodejs/node/blob/bd60e93357a118204ea238d94e7a9e4209d93062/lib/path.js)), +ported in [VS Code 829382514cb1065f5ebb90f436e1c6103e153953](https://github.com/microsoft/vscode/blob/829382514cb1065f5ebb90f436e1c6103e153953/src/vs/base/common/path.ts). + +The full Joyent/Node contributor copyright and permission text is already in +[the original third-party notices](../ThirdPartyNotices.txt). That upstream index +links an older Node commit; this supplement identifies the actual ported source +without removing or rewriting the original notice. Its license text matches both +the v14.16.0 source and VS Code's retained comment. + +VS Code adds types and a browser process adapter, local argument validation and +an Error subclass, exports platform-selected functions, and omits Node's legacy +_makeLong alias. Reviewed algorithm edits include a string guard in Windows join +and shared loop-variable declarations in both basename implementations. These +are existing upstream adaptations, not new ArtPlayer behavior changes. + +Six unused top-level exports are removed from the prepared sources. Both actual +source maps retain identical complete path-module sources after those removals; +TypeScript 4.5.0-dev.20211021 reproduces the full module in editor and worker builds. +The full VS Code compiler pipeline is a separate verification scope. diff --git a/refactor/baselines/site-vendor/monaco-node-path/node-path.js.txt b/refactor/baselines/site-vendor/monaco-node-path/node-path.js.txt new file mode 100644 index 000000000..7532b795b --- /dev/null +++ b/refactor/baselines/site-vendor/monaco-node-path/node-path.js.txt @@ -0,0 +1,1375 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; +const { + CHAR_UPPERCASE_A, + CHAR_LOWERCASE_A, + CHAR_UPPERCASE_Z, + CHAR_LOWERCASE_Z, + CHAR_DOT, + CHAR_FORWARD_SLASH, + CHAR_BACKWARD_SLASH, + CHAR_COLON, + CHAR_QUESTION_MARK, +} = require('internal/constants'); +const { validateString } = require('internal/validators'); + +function isPathSeparator(code) { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +} + +function isPosixPathSeparator(code) { + return code === CHAR_FORWARD_SLASH; +} + +function isWindowsDeviceRoot(code) { + return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || + (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z); +} + +// Resolves . and .. elements in a path with directory names +function normalizeString(path, allowAboveRoot, separator, isPathSeparator) { + let res = ''; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code = 0; + for (let i = 0; i <= path.length; ++i) { + if (i < path.length) + code = path.charCodeAt(i); + else if (isPathSeparator(code)) + break; + else + code = CHAR_FORWARD_SLASH; + + if (isPathSeparator(code)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length !== 0) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + res += res.length > 0 ? `${separator}..` : '..'; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) + res += `${separator}${path.slice(lastSlash + 1, i)}`; + else + res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function _format(sep, pathObject) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ERR_INVALID_ARG_TYPE('pathObject', 'Object', pathObject); + } + const dir = pathObject.dir || pathObject.root; + const base = pathObject.base || + `${pathObject.name || ''}${pathObject.ext || ''}`; + if (!dir) { + return base; + } + return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; +} + +const win32 = { + // path.resolve([from ...], to) + resolve(...args) { + let resolvedDevice = ''; + let resolvedTail = ''; + let resolvedAbsolute = false; + + for (let i = args.length - 1; i >= -1; i--) { + let path; + if (i >= 0) { + path = args[i]; + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + } else if (resolvedDevice.length === 0) { + path = process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive, or the process cwd if + // the drive cwd is not available. We're sure the device is not + // a UNC path at this points, because UNC paths are always absolute. + path = process.env[`=${resolvedDevice}`] || process.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (path === undefined || + (path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && + path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) { + path = `${resolvedDevice}\\`; + } + } + + const len = path.length; + let rootEnd = 0; + let device = ''; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + } else if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len || j !== last) { + // We matched a UNC root + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + if (device.length > 0) { + if (resolvedDevice.length > 0) { + if (device.toLowerCase() !== resolvedDevice.toLowerCase()) + // This path points to another device so it is not applicable + continue; + } else { + resolvedDevice = device; + } + } + + if (resolvedAbsolute) { + if (resolvedDevice.length > 0) + break; + } else { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + if (isAbsolute && resolvedDevice.length > 0) { + break; + } + } + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', + isPathSeparator); + + return resolvedAbsolute ? + `${resolvedDevice}\\${resolvedTail}` : + `${resolvedDevice}${resolvedTail}` || '.'; + }, + + normalize(path) { + validateString(path, 'path'); + const len = path.length; + if (len === 0) + return '.'; + let rootEnd = 0; + let device; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + // `path` contains just a single char, exit early to avoid + // unnecessary work + return isPosixPathSeparator(code) ? '\\' : path; + } + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + return `\\\\${firstPart}\\${path.slice(last)}\\`; + } + if (j !== last) { + // We matched a UNC root with leftovers + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + let tail = rootEnd < len ? + normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : + ''; + if (tail.length === 0 && !isAbsolute) + tail = '.'; + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) + tail += '\\'; + if (device === undefined) { + return isAbsolute ? `\\${tail}` : tail; + } + return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; + }, + + isAbsolute(path) { + validateString(path, 'path'); + const len = path.length; + if (len === 0) + return false; + + const code = path.charCodeAt(0); + return isPathSeparator(code) || + // Possible device root + (len > 2 && + isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON && + isPathSeparator(path.charCodeAt(2))); + }, + + join(...args) { + if (args.length === 0) + return '.'; + + let joined; + let firstPart; + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) + joined = firstPart = arg; + else + joined += `\\${arg}`; + } + } + + if (joined === undefined) + return '.'; + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for a UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at a UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as a UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + if (isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) + ++slashCount; + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + while (slashCount < joined.length && + isPathSeparator(joined.charCodeAt(slashCount))) { + slashCount++; + } + + // Replace the slashes if needed + if (slashCount >= 2) + joined = `\\${joined.slice(slashCount)}`; + } + + return win32.normalize(joined); + }, + + // It will solve the relative path from `from` to `to`, for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + relative(from, to) { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) + return ''; + + const fromOrig = win32.resolve(from); + const toOrig = win32.resolve(to); + + if (fromOrig === toOrig) + return ''; + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) + return ''; + + // Trim any leading backslashes + let fromStart = 0; + while (fromStart < from.length && + from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { + fromStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let fromEnd = from.length; + while (fromEnd - 1 > fromStart && + from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { + fromEnd--; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + while (toStart < to.length && + to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + toStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let toEnd = to.length; + while (toEnd - 1 > toStart && + to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { + toEnd--; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) + break; + else if (fromCode === CHAR_BACKWARD_SLASH) + lastCommonSep = i; + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length) { + if (lastCommonSep === -1) + return toOrig; + } else { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } + if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + if (lastCommonSep === -1) + lastCommonSep = 0; + } + + let out = ''; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + out += out.length === 0 ? '..' : '\\..'; + } + } + + toStart += lastCommonSep; + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) + return `${out}${toOrig.slice(toStart, toEnd)}`; + + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) + ++toStart; + return toOrig.slice(toStart, toEnd); + }, + + toNamespacedPath(path) { + // Note: this will *probably* throw somewhere. + if (typeof path !== 'string') + return path; + + if (path.length === 0) { + return ''; + } + + const resolvedPath = win32.resolve(path); + + if (resolvedPath.length <= 2) + return path; + + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + + return path; + }, + + dirname(path) { + validateString(path, 'path'); + const len = path.length; + if (len === 0) + return '.'; + let rootEnd = -1; + let offset = 0; + const code = path.charCodeAt(0); + + if (len === 1) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work or a dot. + return isPathSeparator(code) ? path : '.'; + } + + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + // Possible device root + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; + offset = rootEnd; + } + + let end = -1; + let matchedSlash = true; + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) + return '.'; + + end = rootEnd; + } + return path.slice(0, end); + }, + + basename(path, ext) { + if (ext !== undefined) + validateString(ext, 'ext'); + validateString(path, 'path'); + let start = 0; + let end = -1; + let matchedSlash = true; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2 && + isWindowsDeviceRoot(path.charCodeAt(0)) && + path.charCodeAt(1) === CHAR_COLON) { + start = 2; + } + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext === path) + return ''; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) + end = firstNonSlashEnd; + else if (end === -1) + end = path.length; + return path.slice(start, end); + } + for (let i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) + return ''; + return path.slice(start, end); + }, + + extname(path) { + validateString(path, 'path'); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if (path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0))) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '\\'), + + parse(path) { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) + return ret; + + const len = path.length; + let rootEnd = 0; + let code = path.charCodeAt(0); + + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + ret.base = ret.name = path; + return ret; + } + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + if (len <= 2) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 2; + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } + if (rootEnd > 0) + ret.root = path.slice(0, rootEnd); + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(startPart, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) + ret.dir = path.slice(0, startPart - 1); + else + ret.dir = ret.root; + + return ret; + }, + + sep: '\\', + delimiter: ';', + win32: null, + posix: null +}; + +const posix = { + // path.resolve([from ...], to) + resolve(...args) { + let resolvedPath = ''; + let resolvedAbsolute = false; + + for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { + const path = i >= 0 ? args[i] : process.cwd(); + + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', + isPosixPathSeparator); + + if (resolvedAbsolute) { + return `/${resolvedPath}`; + } + return resolvedPath.length > 0 ? resolvedPath : '.'; + }, + + normalize(path) { + validateString(path, 'path'); + + if (path.length === 0) + return '.'; + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); + + if (path.length === 0) { + if (isAbsolute) + return '/'; + return trailingSeparator ? './' : '.'; + } + if (trailingSeparator) + path += '/'; + + return isAbsolute ? `/${path}` : path; + }, + + isAbsolute(path) { + validateString(path, 'path'); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; + }, + + join(...args) { + if (args.length === 0) + return '.'; + let joined; + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) + joined = arg; + else + joined += `/${arg}`; + } + } + if (joined === undefined) + return '.'; + return posix.normalize(joined); + }, + + relative(from, to) { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) + return ''; + + // Trim leading forward slashes. + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) + return ''; + + const fromStart = 1; + const fromEnd = from.length; + const fromLen = fromEnd - fromStart; + const toStart = 1; + const toLen = to.length - toStart; + + // Compare paths to find the longest common path from root + const length = (fromLen < toLen ? fromLen : toLen); + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) + break; + else if (fromCode === CHAR_FORWARD_SLASH) + lastCommonSep = i; + } + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } + if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + lastCommonSep = 0; + } + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` + // and `from`. + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + out += out.length === 0 ? '..' : '/..'; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts. + return `${out}${to.slice(toStart + lastCommonSep)}`; + }, + + toNamespacedPath(path) { + // Non-op on posix systems + return path; + }, + + dirname(path) { + validateString(path, 'path'); + if (path.length === 0) + return '.'; + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) + return hasRoot ? '/' : '.'; + if (hasRoot && end === 1) + return '//'; + return path.slice(0, end); + }, + + basename(path, ext) { + if (ext !== undefined) + validateString(ext, 'ext'); + validateString(path, 'path'); + + let start = 0; + let end = -1; + let matchedSlash = true; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext === path) + return ''; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) + end = firstNonSlashEnd; + else if (end === -1) + end = path.length; + return path.slice(start, end); + } + for (let i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) + return ''; + return path.slice(start, end); + }, + + extname(path) { + validateString(path, 'path'); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '/'), + + parse(path) { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) + return ret; + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + const start = startPart === 0 && isAbsolute ? 1 : startPart; + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(start, end); + } else { + ret.name = path.slice(start, startDot); + ret.base = path.slice(start, end); + ret.ext = path.slice(startDot, end); + } + } + + if (startPart > 0) + ret.dir = path.slice(0, startPart - 1); + else if (isAbsolute) + ret.dir = '/'; + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; + +// Legacy internal API, docs-only deprecated: DEP0080 +win32._makeLong = win32.toNamespacedPath; +posix._makeLong = posix.toNamespacedPath; + +module.exports = process.platform === 'win32' ? win32 : posix; diff --git a/refactor/baselines/site-vendor/monaco-node-path/vscode-path.ts.txt b/refactor/baselines/site-vendor/monaco-node-path/vscode-path.ts.txt new file mode 100644 index 000000000..00d948acd --- /dev/null +++ b/refactor/baselines/site-vendor/monaco-node-path/vscode-path.ts.txt @@ -0,0 +1,1506 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace +// Copied from: https://github.com/nodejs/node/blob/v14.16.0/lib/path.js + +/** + * Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import * as process from 'vs/base/common/process'; + +const CHAR_UPPERCASE_A = 65;/* A */ +const CHAR_LOWERCASE_A = 97; /* a */ +const CHAR_UPPERCASE_Z = 90; /* Z */ +const CHAR_LOWERCASE_Z = 122; /* z */ +const CHAR_DOT = 46; /* . */ +const CHAR_FORWARD_SLASH = 47; /* / */ +const CHAR_BACKWARD_SLASH = 92; /* \ */ +const CHAR_COLON = 58; /* : */ +const CHAR_QUESTION_MARK = 63; /* ? */ + +class ErrorInvalidArgType extends Error { + code: 'ERR_INVALID_ARG_TYPE'; + constructor(name: string, expected: string, actual: unknown) { + // determiner: 'must be' or 'must not be' + let determiner; + if (typeof expected === 'string' && expected.indexOf('not ') === 0) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + const type = name.indexOf('.') !== -1 ? 'property' : 'argument'; + let msg = `The "${name}" ${type} ${determiner} of type ${expected}`; + + msg += `. Received type ${typeof actual}`; + super(msg); + + this.code = 'ERR_INVALID_ARG_TYPE'; + } +} + +function validateString(value: string, name: string) { + if (typeof value !== 'string') { + throw new ErrorInvalidArgType(name, 'string', value); + } +} + +function isPathSeparator(code: number | undefined) { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +} + +function isPosixPathSeparator(code: number | undefined) { + return code === CHAR_FORWARD_SLASH; +} + +function isWindowsDeviceRoot(code: number) { + return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || + (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z); +} + +// Resolves . and .. elements in a path with directory names +function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code?: number) => boolean) { + let res = ''; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code = 0; + for (let i = 0; i <= path.length; ++i) { + if (i < path.length) { + code = path.charCodeAt(i); + } + else if (isPathSeparator(code)) { + break; + } + else { + code = CHAR_FORWARD_SLASH; + } + + if (isPathSeparator(code)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length !== 0) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + res += res.length > 0 ? `${separator}..` : '..'; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) { + res += `${separator}${path.slice(lastSlash + 1, i)}`; + } + else { + res = path.slice(lastSlash + 1, i); + } + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function _format(sep: string, pathObject: ParsedPath) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); + } + const dir = pathObject.dir || pathObject.root; + const base = pathObject.base || + `${pathObject.name || ''}${pathObject.ext || ''}`; + if (!dir) { + return base; + } + return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; +} + +export interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; +} + +export interface IPath { + normalize(path: string): string; + isAbsolute(path: string): boolean; + join(...paths: string[]): string; + resolve(...pathSegments: string[]): string; + relative(from: string, to: string): string; + dirname(path: string): string; + basename(path: string, ext?: string): string; + extname(path: string): string; + format(pathObject: ParsedPath): string; + parse(path: string): ParsedPath; + toNamespacedPath(path: string): string; + sep: '\\' | '/'; + delimiter: string; + win32: IPath | null; + posix: IPath | null; +} + +export const win32: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedDevice = ''; + let resolvedTail = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path; + if (i >= 0) { + path = pathSegments[i]; + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + } else if (resolvedDevice.length === 0) { + path = process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive, or the process cwd if + // the drive cwd is not available. We're sure the device is not + // a UNC path at this points, because UNC paths are always absolute. + path = process.env[`=${resolvedDevice}`] || process.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (path === undefined || + (path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && + path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) { + path = `${resolvedDevice}\\`; + } + } + + const len = path.length; + let rootEnd = 0; + let device = ''; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + } else if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len || j !== last) { + // We matched a UNC root + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + if (device.length > 0) { + if (resolvedDevice.length > 0) { + if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + } else { + resolvedDevice = device; + } + } + + if (resolvedAbsolute) { + if (resolvedDevice.length > 0) { + break; + } + } else { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + if (isAbsolute && resolvedDevice.length > 0) { + break; + } + } + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', + isPathSeparator); + + return resolvedAbsolute ? + `${resolvedDevice}\\${resolvedTail}` : + `${resolvedDevice}${resolvedTail}` || '.'; + }, + + normalize(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = 0; + let device; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + // `path` contains just a single char, exit early to avoid + // unnecessary work + return isPosixPathSeparator(code) ? '\\' : path; + } + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + return `\\\\${firstPart}\\${path.slice(last)}\\`; + } + if (j !== last) { + // We matched a UNC root with leftovers + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + let tail = rootEnd < len ? + normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : + ''; + if (tail.length === 0 && !isAbsolute) { + tail = '.'; + } + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { + tail += '\\'; + } + if (device === undefined) { + return isAbsolute ? `\\${tail}` : tail; + } + return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return false; + } + + const code = path.charCodeAt(0); + return isPathSeparator(code) || + // Possible device root + (len > 2 && + isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON && + isPathSeparator(path.charCodeAt(2))); + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + + let joined; + let firstPart: string | undefined; + for (let i = 0; i < paths.length; ++i) { + const arg = paths[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = firstPart = arg; + } + else { + joined += `\\${arg}`; + } + } + } + + if (joined === undefined) { + return '.'; + } + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for a UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at a UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as a UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) { + ++slashCount; + } else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + while (slashCount < joined.length && + isPathSeparator(joined.charCodeAt(slashCount))) { + slashCount++; + } + + // Replace the slashes if needed + if (slashCount >= 2) { + joined = `\\${joined.slice(slashCount)}`; + } + } + + return win32.normalize(joined); + }, + + + // It will solve the relative path from `from` to `to`, for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + const fromOrig = win32.resolve(from); + const toOrig = win32.resolve(to); + + if (fromOrig === toOrig) { + return ''; + } + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) { + return ''; + } + + // Trim any leading backslashes + let fromStart = 0; + while (fromStart < from.length && + from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { + fromStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let fromEnd = from.length; + while (fromEnd - 1 > fromStart && + from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { + fromEnd--; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + while (toStart < to.length && + to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + toStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let toEnd = to.length; + while (toEnd - 1 > toStart && + to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { + toEnd--; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) { + break; + } else if (fromCode === CHAR_BACKWARD_SLASH) { + lastCommonSep = i; + } + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length) { + if (lastCommonSep === -1) { + return toOrig; + } + } else { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } + if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + if (lastCommonSep === -1) { + lastCommonSep = 0; + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + out += out.length === 0 ? '..' : '\\..'; + } + } + + toStart += lastCommonSep; + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return `${out}${toOrig.slice(toStart, toEnd)}`; + } + + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + ++toStart; + } + + return toOrig.slice(toStart, toEnd); + }, + + toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== 'string') { + return path; + } + + if (path.length === 0) { + return ''; + } + + const resolvedPath = win32.resolve(path); + + if (resolvedPath.length <= 2) { + return path; + } + + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = -1; + let offset = 0; + const code = path.charCodeAt(0); + + if (len === 1) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work or a dot. + return isPathSeparator(code) ? path : '.'; + } + + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + // Possible device root + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; + offset = rootEnd; + } + + let end = -1; + let matchedSlash = true; + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) { + return '.'; + } + + end = rootEnd; + } + return path.slice(0, end); + }, + + basename(path: string, ext?: string): string { + if (ext !== undefined) { + validateString(ext, 'ext'); + } + validateString(path, 'path'); + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2 && + isWindowsDeviceRoot(path.charCodeAt(0)) && + path.charCodeAt(1) === CHAR_COLON) { + start = 2; + } + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext === path) { + return ''; + } + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + }, + + extname(path: string): string { + validateString(path, 'path'); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if (path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0))) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '\\'), + + parse(path) { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + + const len = path.length; + let rootEnd = 0; + let code = path.charCodeAt(0); + + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + ret.base = ret.name = path; + return ret; + } + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + if (len <= 2) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 2; + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } + if (rootEnd > 0) { + ret.root = path.slice(0, rootEnd); + } + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(startPart, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } else { + ret.dir = ret.root; + } + + return ret; + }, + + sep: '\\', + delimiter: ';', + win32: null, + posix: null +}; + +export const posix: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedPath = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + const path = i >= 0 ? pathSegments[i] : process.cwd(); + + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', + isPosixPathSeparator); + + if (resolvedAbsolute) { + return `/${resolvedPath}`; + } + return resolvedPath.length > 0 ? resolvedPath : '.'; + }, + + normalize(path: string): string { + validateString(path, 'path'); + + if (path.length === 0) { + return '.'; + } + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); + + if (path.length === 0) { + if (isAbsolute) { + return '/'; + } + return trailingSeparator ? './' : '.'; + } + if (trailingSeparator) { + path += '/'; + } + + return isAbsolute ? `/${path}` : path; + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + let joined; + for (let i = 0; i < paths.length; ++i) { + const arg = paths[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = arg; + } else { + joined += `/${arg}`; + } + } + } + if (joined === undefined) { + return '.'; + } + return posix.normalize(joined); + }, + + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + // Trim leading forward slashes. + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) { + return ''; + } + + const fromStart = 1; + const fromEnd = from.length; + const fromLen = fromEnd - fromStart; + const toStart = 1; + const toLen = to.length - toStart; + + // Compare paths to find the longest common path from root + const length = (fromLen < toLen ? fromLen : toLen); + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) { + break; + } else if (fromCode === CHAR_FORWARD_SLASH) { + lastCommonSep = i; + } + } + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } + if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + lastCommonSep = 0; + } + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` + // and `from`. + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + out += out.length === 0 ? '..' : '/..'; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts. + return `${out}${to.slice(toStart + lastCommonSep)}`; + }, + + toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + if (path.length === 0) { + return '.'; + } + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + return hasRoot ? '/' : '.'; + } + if (hasRoot && end === 1) { + return '//'; + } + return path.slice(0, end); + }, + + basename(path: string, ext?: string): string { + if (ext !== undefined) { + validateString(ext, 'ext'); + } + validateString(path, 'path'); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext === path) { + return ''; + } + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + }, + + extname(path: string): string { + validateString(path, 'path'); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '/'), + + parse(path: string): ParsedPath { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + const start = startPart === 0 && isAbsolute ? 1 : startPart; + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(start, end); + } else { + ret.name = path.slice(start, startDot); + ret.base = path.slice(start, end); + ret.ext = path.slice(startDot, end); + } + } + + if (startPart > 0) { + ret.dir = path.slice(0, startPart - 1); + } else if (isAbsolute) { + ret.dir = '/'; + } + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; + +export const normalize = (process.platform === 'win32' ? win32.normalize : posix.normalize); +export const isAbsolute = (process.platform === 'win32' ? win32.isAbsolute : posix.isAbsolute); +export const join = (process.platform === 'win32' ? win32.join : posix.join); +export const resolve = (process.platform === 'win32' ? win32.resolve : posix.resolve); +export const relative = (process.platform === 'win32' ? win32.relative : posix.relative); +export const dirname = (process.platform === 'win32' ? win32.dirname : posix.dirname); +export const basename = (process.platform === 'win32' ? win32.basename : posix.basename); +export const extname = (process.platform === 'win32' ? win32.extname : posix.extname); +export const format = (process.platform === 'win32' ? win32.format : posix.format); +export const parse = (process.platform === 'win32' ? win32.parse : posix.parse); +export const toNamespacedPath = (process.platform === 'win32' ? win32.toNamespacedPath : posix.toNamespacedPath); +export const sep = (process.platform === 'win32' ? win32.sep : posix.sep); +export const delimiter = (process.platform === 'win32' ? win32.delimiter : posix.delimiter); diff --git a/refactor/changes/2026-09-15-SITE-07-monaco-node-path.md b/refactor/changes/2026-09-15-SITE-07-monaco-node-path.md new file mode 100644 index 000000000..d82ae6447 --- /dev/null +++ b/refactor/changes/2026-09-15-SITE-07-monaco-node-path.md @@ -0,0 +1,55 @@ +# SITE-07 Monaco Node 路径库来源与交付绑定 + +修改前 HEAD:d2b2f76db84ba107751766935a79b78766b81c7d。 + +## 来源、实现与兼容 + +VS Code path.ts 明确注明来自 Node v14.16.0。本批将该 tag 解析到固定 commit +bd60e93357a118204ea238d94e7a9e4209d93062,冻结 Node 原源码及 VS Code 移植源码, +并使用 SHA-256/Git blob 校验。原站点 ThirdPartyNotices 的完整 Joyent/Node +许可与两份源码正文一致;仅其中链接指向更早 commit。保留原文件,补充一份 +core-path/ATTRIBUTION.md,准确说明实际来源和既有移植变化,没有虚构许可缺失。 + +新增 node-path.ts / reproduce-node-path.ts,将来源、6 个未用导出的移除、原版 +路径参考用例及 notice 绑定分开维护。Node 路径算法经既有 VS Code 移植,包含 +类型/参数名/括号整理、Windows join 字符串保护、两处 basename 循环变量调整、 +本地参数校验和 Error 类、process adapter、显式导出及旧 _makeLong 别名的省略。 +不声称等同未经修改的 Node 运行时,本批没有改动这些既有运行逻辑。 + +移除六个精确指定的顶层导出后,完整源码与主编辑器/worker 两份原 source map +一致。初始记录误用了整理缓存中的 file URI;读取原归档确认两个 map 实际使用 +不同的 out-editor/.../file:/ 前缀,现按原始 key 精确核对。固定 VS Code 原锁中 +TypeScript 4.5.0-dev.20211021,发射的完整路径模块分别匹配主编辑器 57,628 字节 +和 worker 57,627 字节的唯一源码片段。对应站点压缩文件与固定归档完全相同, +整体压缩链复用既有 core-build 证据;这不是完整原 VS Code TS 工程编译证明。 + +历史编译器仅解包到隔离缓存,不覆盖已有 Monaco TypeScript 4.4.4 复现工具, +不改项目依赖、根 yarn.lock、运行资源、公开 API 或 worker URL。 + +## 测试与交付 + +冻结 Node 源码实际计算 30 个确定性合法路径用例,涵盖 POSIX/Windows 的空值、 +点段、UNC、路径组合/相对关系、扩展名、中文路径、格式化和命名空间路径。 +参考环境限制额外 import 与 cwd 使用,不模拟完整 Node 错误文本或驱动器环境。 +三引擎测试直接调用真实 Monaco 两个 namespace,并检查平台别名和原有 +Error/ERR_INVALID_ARG_TYPE 行为。另一组三引擎测试验证实际移动播放、全部 +86 份 notice 的 HTTP/内容以及新增相对链接,总计 6/6 通过。 + +28/28 单元、严格 docs-tools 类型、lint、联网/离线来源与发射复验通过;普通 +notice 构建新增 Node 双资产/原许可/补充说明绑定,负例覆盖遗漏 worker 或原 +条款、错误/重复导出、额外 Node import 和非确定性 cwd。总交付为 86 notices +加索引,即 87 outputs。 + +Chromium 移动测试记录一条 pattern.mp4 net::ERR_ABORTED,保留在证据中;实际 +播放时间推进断言通过,没有将本轮描述为所有网络请求零失败。其余本轮浏览器 +错误/控制台错误为空。详情见[验证记录](../baselines/monaco-node-path-validation.json)。 + +## 后续与回退 + +维护入口:[Monaco README](../../scripts/site-vendor/monaco/README.md)。Node 路径 +条目的版本/来源/原有条款/实际模块对应已核实;WinJS、Unicode 数据和其他 +内嵌来源、完整 core TS 编译以及 SITE-07 其他交付范围继续开放。CSS 70 个 +生成分隔符的发布归一化位置仍按前一批记录保留限制。 + +SITE-07 doing / VENDOR-06 open,199/265;Thumbnail 默认策略仍待答。独立本地 +提交;回退时同批撤回新增来源/测试/绑定和重新生成 notice 索引,无推送或发布。 diff --git a/refactor/plan.md b/refactor/plan.md index 300baa2ce..0033b470f 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -653,7 +653,7 @@ - SITE-AI-DOCS-01: [记录](changes/2026-09-14-SITE-AI-DOCS-01-documentation-pipeline.md) [记录](baselines/documentation-pipeline-validation.json) - SITE-BUILD-01: [记录](changes/2026-09-14-SITE-BUILD-01-staged-builds.md) [记录](baselines/site-build-validation.json) - SITE-03: [记录](changes/2026-09-14-SITE-03-desktop-editor.md) [记录](baselines/site-editor-validation.json) -- SITE-07: [记录](site-inventory.md) [记录](baselines/site-provenance.json) [记录](changes/2026-09-14-SITE-07-vendor-notices.md) [记录](baselines/site-notices-checkpoint.json) [记录](baselines/site-codicons-provenance.json) [记录](baselines/site-codicons-validation.json) [记录](changes/2026-09-15-SITE-07-codicons.md) [记录](baselines/vconsole-notices-provenance.json) [记录](baselines/vconsole-notices-validation.json) [记录](changes/2026-09-15-SITE-07-vconsole-notices.md) [记录](console-modernization.md) [记录](baselines/site-console-inventory.json) [记录](baselines/site-console-validation.json) [记录](changes/2026-09-15-SITE-07-console-baseline.md) [记录](baselines/console-feed-provenance.json) [记录](changes/2026-09-15-SITE-07-console-feed-source.md) [记录](baselines/console-commonjs-provenance.json) [记录](changes/2026-09-15-SITE-07-console-commonjs.md) [记录](baselines/console-esm-provenance.json) [记录](changes/2026-09-15-SITE-07-console-esm.md) [记录](baselines/console-embedded-notices.json) [记录](baselines/console-notices-validation.json) [记录](changes/2026-09-15-SITE-07-console-notices.md) [记录](baselines/console-embedded-sources.json) [记录](baselines/console-embedded-validation.json) [记录](changes/2026-09-15-SITE-07-console-embedded-sources.md) [记录](baselines/console-derived-attribution.json) [记录](baselines/console-derived-validation.json) [记录](changes/2026-09-15-SITE-07-console-derived-attribution.md) [记录](baselines/console-stackoverflow-provenance.json) [记录](baselines/console-stackoverflow-validation.json) [记录](changes/2026-09-15-SITE-07-console-stackoverflow.md) [记录](baselines/console-shallowequal-validation.json) [记录](changes/2026-09-15-SITE-07-console-shallowequal.md) [记录](console-notice-review.md) [记录](baselines/console-notice-review-validation.json) [记录](changes/2026-09-15-SITE-07-console-notice-review.md) [记录](baselines/monaco-typescript-provenance.json) [记录](baselines/monaco-typescript-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-typescript.md) [记录](baselines/monaco-languages-provenance.json) [记录](baselines/monaco-languages-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-languages.md) [记录](baselines/monaco-language-notices.json) [记录](baselines/monaco-language-notices-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-language-notices.md) [记录](baselines/monaco-modes-provenance.json) [记录](baselines/monaco-modes-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-modes.md) [记录](baselines/monaco-basic-provenance.json) [记录](baselines/monaco-basic-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-basic.md) [记录](baselines/monaco-core-origins-provenance.json) [记录](baselines/monaco-core-origins-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-origins.md) [记录](baselines/monaco-core-build-provenance.json) [记录](baselines/monaco-core-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-build.md) [记录](baselines/monaco-contributions-provenance.json) [记录](baselines/monaco-contributions-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-contributions.md) [记录](baselines/monaco-css-build-provenance.json) [记录](baselines/monaco-css-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-css.md) +- SITE-07: [记录](site-inventory.md) [记录](baselines/site-provenance.json) [记录](changes/2026-09-14-SITE-07-vendor-notices.md) [记录](baselines/site-notices-checkpoint.json) [记录](baselines/site-codicons-provenance.json) [记录](baselines/site-codicons-validation.json) [记录](changes/2026-09-15-SITE-07-codicons.md) [记录](baselines/vconsole-notices-provenance.json) [记录](baselines/vconsole-notices-validation.json) [记录](changes/2026-09-15-SITE-07-vconsole-notices.md) [记录](console-modernization.md) [记录](baselines/site-console-inventory.json) [记录](baselines/site-console-validation.json) [记录](changes/2026-09-15-SITE-07-console-baseline.md) [记录](baselines/console-feed-provenance.json) [记录](changes/2026-09-15-SITE-07-console-feed-source.md) [记录](baselines/console-commonjs-provenance.json) [记录](changes/2026-09-15-SITE-07-console-commonjs.md) [记录](baselines/console-esm-provenance.json) [记录](changes/2026-09-15-SITE-07-console-esm.md) [记录](baselines/console-embedded-notices.json) [记录](baselines/console-notices-validation.json) [记录](changes/2026-09-15-SITE-07-console-notices.md) [记录](baselines/console-embedded-sources.json) [记录](baselines/console-embedded-validation.json) [记录](changes/2026-09-15-SITE-07-console-embedded-sources.md) [记录](baselines/console-derived-attribution.json) [记录](baselines/console-derived-validation.json) [记录](changes/2026-09-15-SITE-07-console-derived-attribution.md) [记录](baselines/console-stackoverflow-provenance.json) [记录](baselines/console-stackoverflow-validation.json) [记录](changes/2026-09-15-SITE-07-console-stackoverflow.md) [记录](baselines/console-shallowequal-validation.json) [记录](changes/2026-09-15-SITE-07-console-shallowequal.md) [记录](console-notice-review.md) [记录](baselines/console-notice-review-validation.json) [记录](changes/2026-09-15-SITE-07-console-notice-review.md) [记录](baselines/monaco-typescript-provenance.json) [记录](baselines/monaco-typescript-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-typescript.md) [记录](baselines/monaco-languages-provenance.json) [记录](baselines/monaco-languages-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-languages.md) [记录](baselines/monaco-language-notices.json) [记录](baselines/monaco-language-notices-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-language-notices.md) [记录](baselines/monaco-modes-provenance.json) [记录](baselines/monaco-modes-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-modes.md) [记录](baselines/monaco-basic-provenance.json) [记录](baselines/monaco-basic-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-basic.md) [记录](baselines/monaco-core-origins-provenance.json) [记录](baselines/monaco-core-origins-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-origins.md) [记录](baselines/monaco-core-build-provenance.json) [记录](baselines/monaco-core-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-build.md) [记录](baselines/monaco-contributions-provenance.json) [记录](baselines/monaco-contributions-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-contributions.md) [记录](baselines/monaco-css-build-provenance.json) [记录](baselines/monaco-css-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-css.md) [记录](baselines/monaco-node-path-provenance.json) [记录](baselines/monaco-node-path-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-node-path.md) - EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs) - EX-02: [记录](changes/2026-09-14-EX-02-vue-consumer.md) [记录](baselines/vue-consumer-validation.json) [记录](scripts/vue-consumer.mjs) - MOD-01: [记录](changes/2026-09-15-MOD-01-bun-evaluation.md) [记录](baselines/bun-install-validation.json) [记录](bun-evaluation.md) diff --git a/refactor/progress.md b/refactor/progress.md index 62cb091d7..522441ae8 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,16 @@ # 进度与证据 +## SITE-07 Monaco Node 路径来源与完整模块 + +核实 Node v14.16.0 固定源码及 VS Code 移植。原 notice 已含完整相同条款,新增 +准确来源说明;86 份 notice 加索引。6 个未用导出移除后匹配两份原 map,历史 +TS 4.5.0-dev 编译匹配主编辑器/worker 完整模块。28/28 单元、类型/lint、联网/ +离线复验及三引擎 30 路径用例/移动实播/notice 交付 6/6 通过;Chromium 一条 +媒体 ERR_ABORTED 保留记录,播放推进断言通过。见[记录](changes/2026-09-15-SITE-07-monaco-node-path.md)。 +其余内嵌来源、完整 core TS 编译与既有 CSS 发布换行定位限制继续开放。 +SITE-07/VENDOR-06 未完结,199/265;Thumbnail 默认策略仍待答。 + + ## SITE-07 Monaco CSS 来源与样式回归 68 份样式、3 个内联图片和字体按原配方恢复,81 个 Git 成员验证;显式记录 diff --git a/refactor/risks.json b/refactor/risks.json index 352a08409..61335f97d 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -658,11 +658,14 @@ "refactor/changes/2026-09-15-SITE-07-monaco-contributions.md", "refactor/baselines/monaco-css-build-provenance.json", "refactor/baselines/monaco-css-build-validation.json", - "refactor/changes/2026-09-15-SITE-07-monaco-css.md" + "refactor/changes/2026-09-15-SITE-07-monaco-css.md", + "refactor/baselines/monaco-node-path-provenance.json", + "refactor/baselines/monaco-node-path-validation.json", + "refactor/changes/2026-09-15-SITE-07-monaco-node-path.md" ], "compatibleResolution": "Keep separate from owned TS migration. Verify source/version/diff and license notices before replacement; preserve API/CSS/worker URLs and run owning package tests.", "closureCriteria": "固定上游版本/内容差异、完整组件许可与分发 notices,兼容测试通过;仅当前上游许可证名称不足以关闭。", - "workspaceState": "Monaco 0.30.1 remains byte-pinned, including its known CSS newline-only difference. Codicons 0.0.26, TypeScript 4.4.4 and language-service licenses are delivered. Three service workers, four modes and 76 basic-language bundles reproduce exactly; all 13 core JS minified outputs reproduce with locked esbuild 0.12.6 on Windows x64. Loader/header and css/nls source origins match fixed Git; two maps inventory 584 prepared-source entries and 549 different names. DOMPurify 2.3.1 and marked 3.0.2 have exact source adaptations and complete notice supplements; 85 site notices were verified by HTTP/mobile browser checks. Latest contribution check verifies six archives and 102 Git members, emits 88 TS registration modules and uses RequireJS 2.3.6/Terser 5.9.0 to reproduce the entire dev/min editor assembly, including core string-table entry, dependency injection, order, alias and map trailer. Latest units 23/23, strict type/lint and offline/network source checks pass; modes/tokenizer regressions pass 6/6 across three engines with 2523 token cases per engine. Earlier ten-locale editing/find 30/30 and diff-worker 3/3 evidence remains recorded separately. CSS now reproduces from 68 styles, three embedded images and the copied font, with 81 verified Git members. The archived transport/plugin recipes match every prepared ESM stylesheet; explicit normalization of 70 generated CRLF separators reproduces the entire development CSS. cssnano 4.1.11/PostCSS 7.0.35 reproduce complete minified CSS using isolated frozen Yarn installs (153 selectors/143 versions) and historical browser defaults. The site retains its five header CRLF differences. The exact upstream stage that normalized the 70 development separators is still unidentified. Latest units 25/25, strict type/lint and offline/network checks pass; new three-engine style/theme/font/resize regressions pass 3/3. Original core TS compilation and remaining embedded-origin review stay open; this is not release approval or an exhaustive sanitizer audit." + "workspaceState": "Monaco 0.30.1 remains byte-pinned, including its known CSS newline-only difference. Codicons 0.0.26, TypeScript 4.4.4 and language-service licenses are delivered. Three service workers, four modes and 76 basic-language bundles reproduce exactly; all 13 core JS minified outputs reproduce with locked esbuild 0.12.6 on Windows x64. Loader/header and css/nls source origins match fixed Git; two maps inventory 584 prepared-source entries and 549 different names. DOMPurify 2.3.1 and marked 3.0.2 have exact source adaptations and complete notice supplements; 85 site notices were verified by HTTP/mobile browser checks. Latest contribution check verifies six archives and 102 Git members, emits 88 TS registration modules and uses RequireJS 2.3.6/Terser 5.9.0 to reproduce the entire dev/min editor assembly, including core string-table entry, dependency injection, order, alias and map trailer. Latest units 23/23, strict type/lint and offline/network source checks pass; modes/tokenizer regressions pass 6/6 across three engines with 2523 token cases per engine. Earlier ten-locale editing/find 30/30 and diff-worker 3/3 evidence remains recorded separately. CSS now reproduces from 68 styles, three embedded images and the copied font, with 81 verified Git members. The archived transport/plugin recipes match every prepared ESM stylesheet; explicit normalization of 70 generated CRLF separators reproduces the entire development CSS. cssnano 4.1.11/PostCSS 7.0.35 reproduce complete minified CSS using isolated frozen Yarn installs (153 selectors/143 versions) and historical browser defaults. The site retains its five header CRLF differences. The exact upstream stage that normalized the 70 development separators is still unidentified. Latest units 25/25, strict type/lint and offline/network checks pass; new three-engine style/theme/font/resize regressions pass 3/3. Original core TS compilation and remaining embedded-origin review stay open; this is not release approval or an exhaustive sanitizer audit. The Node path origin is now fixed to v14.16.0/bd60e93357a118204ea238d94e7a9e4209d93062 and the verified VS Code port. Its complete existing notice text matches both sources; a source supplement corrects the older index link without rewriting it. Six removed exports reproduce both source-map inputs; pinned TS 4.5.0-dev.20211021 emits exact full editor/worker modules. Thirty deterministic original Node path results pass in each browser engine, together with all 86 delivered notices and actual playback: 6/6 browser, 28/28 unit, type/lint, offline/network checks. Chromium retains one observed media ERR_ABORTED despite successful playback. Other embedded origins/full core compilation remain open." }, { "id": "VENDOR-07", diff --git a/refactor/tasks.json b/refactor/tasks.json index 65d7cea03..0e6fe6569 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -4540,7 +4540,10 @@ "changes/2026-09-15-SITE-07-monaco-contributions.md", "baselines/monaco-css-build-provenance.json", "baselines/monaco-css-build-validation.json", - "changes/2026-09-15-SITE-07-monaco-css.md" + "changes/2026-09-15-SITE-07-monaco-css.md", + "baselines/monaco-node-path-provenance.json", + "baselines/monaco-node-path-validation.json", + "changes/2026-09-15-SITE-07-monaco-node-path.md" ] }, { diff --git a/scripts/build-site-notices.mjs b/scripts/build-site-notices.mjs index 6ebeb52ef..886fb03f4 100644 --- a/scripts/build-site-notices.mjs +++ b/scripts/build-site-notices.mjs @@ -3,6 +3,7 @@ import fs from 'node:fs' import process from 'node:process' import { verifyConsoleNoticeSources } from './site-vendor/console/notices.ts' import { verifyMonacoCoreNotices } from './site-vendor/monaco/core-origins.ts' +import { verifyMonacoPathNotices } from './site-vendor/monaco/node-path.ts' import { verifyMonacoLanguageNotices } from './site-vendor/monaco/notices.ts' import { writeOrCheckNotices } from './site-vendor/notices.ts' @@ -22,6 +23,7 @@ assert.deepEqual(manifest.groups.filter(group => group.name !== 'console').flatM 'jsonc-parser', 'marked (Monaco core)', 'mutation-observer', + 'nodejs path (Monaco core)', 'regenerator-runtime', 'style-loader', 'svelte', @@ -89,6 +91,7 @@ for (const name of ['LICENSE', 'MIT-LICENSE', 'ATTRIBUTION.md']) assert(vconsoleNotices?.includes(`docs/licenses/vconsole/${name}`), `Missing vConsole notice: ${name}`) verifyConsoleNoticeSources(process.cwd(), manifest) verifyMonacoCoreNotices(process.cwd(), manifest) +verifyMonacoPathNotices(process.cwd(), manifest) verifyMonacoLanguageNotices(process.cwd(), manifest) const count = writeOrCheckNotices(process.cwd(), manifest, process.argv.includes('--check')) console.log(`Verified site notices: ${count} outputs; Monaco/vConsole/console inventories, embedded and other provenance gates remain open.`) diff --git a/scripts/site-vendor/manifest.json b/scripts/site-vendor/manifest.json index 78f5ccd28..b364eceb8 100644 --- a/scripts/site-vendor/manifest.json +++ b/scripts/site-vendor/manifest.json @@ -1020,6 +1020,11 @@ "source": "refactor/baselines/site-vendor/monaco-core-origins/ATTRIBUTION.md", "target": "docs/licenses/monaco-editor/core-origins/ATTRIBUTION.md", "sha256": "3a0ab7f1036d6b1397bf94ff2700375b97db2db3bd576d195dc6d8a88e1a758e" + }, + { + "source": "refactor/baselines/site-vendor/monaco-node-path/ATTRIBUTION.md.txt", + "target": "docs/licenses/monaco-editor/core-path/ATTRIBUTION.md", + "sha256": "40583c701c0bcbd8e10935652a0a8b02e2073b0ef040fb7be7b939c437d8b8dc" } ], "review": "The complete upstream Monaco LICENSE/ThirdPartyNotices are retained. The unmodified bundled Codicons font exactly matches @vscode/codicons 0.0.26; its historical README, CC BY 4.0 content license, MIT code license and added attribution are distributed below. Other site assets remain subject to their separate provenance reviews.", @@ -1199,6 +1204,19 @@ "docs/licenses/monaco-editor/core-origins/marked-vscode-license.txt", "docs/licenses/monaco-editor/core-origins/ATTRIBUTION.md" ] + }, + { + "name": "nodejs path (Monaco core)", + "version": "14.16.0", + "tarball": "https://github.com/nodejs/node/blob/bd60e93357a118204ea238d94e7a9e4209d93062/lib/path.js", + "assets": [ + "docs/assets/js/vs/editor/editor.main.js", + "docs/assets/js/vs/base/worker/workerMain.js" + ], + "notices": [ + "docs/licenses/monaco-editor/ThirdPartyNotices.txt", + "docs/licenses/monaco-editor/core-path/ATTRIBUTION.md" + ] } ] }, diff --git a/scripts/site-vendor/monaco/README.md b/scripts/site-vendor/monaco/README.md index 90ac1cb5f..c7d97e086 100644 --- a/scripts/site-vendor/monaco/README.md +++ b/scripts/site-vendor/monaco/README.md @@ -325,3 +325,48 @@ Browser checks cover light/dark theme colors, layout and resize, find-widget bou successful CSS/font requests, loaded codicon glyphs and model disposal in all three engines. The pinned light theme is intentionally #fffffe. Full original core TS compilation and further embedded-origin review remain separate open work. + +## Node path port and existing terms + +`node-path.ts` binds the Node path attribution to both editor.main and workerMain. +The original site ThirdPartyNotices already contains the full Joyent/Node license +text, identical to Node v14.16.0 and VS Code's retained source comment. Its older +commit link is kept intact; a new core-path/ATTRIBUTION.md identifies the actual +fixed Node source and VS Code port. The site now delivers 86 notice files plus +its index. The ordinary notice build rejects a missing worker binding, original +terms, source explanation, or mismatched ported license/version. + +`reproduce-node-path.ts` checks three archives and three fixed Git files. Six +explicit unused top-level exports are removed from the VS Code source; the complete +result equals both archived source-map entries. Their source keys contain different +`out-editor/.../file:/...` prefixes, so the record uses the actual keys, not a guessed +file URI. Historical TypeScript 4.5.0-dev.20211021 from VS Code's lock compiles the +full path module into both development bundles with exact unique spans. It stays +in a separate ignored compiler directory from Monaco's TypeScript 4.4.4 tools. +The corresponding shipped minified assets are checked against their fixed archives; +the complete core minifier was verified separately by reproduce-core-build.ts. + +This is a modified browser port, not an unmodified Node runtime. Besides types, +parameter names and brace style, review identified a Windows join string guard, +shared basename loop variables, local validation/Error handling, the process +adapter, explicit exports and omission of Node's deprecated _makeLong alias. +The new source note records these existing adaptations without changing behavior. + +Thirty deterministic valid calls are evaluated by the frozen Node v14.16.0 module +and frozen as fixtures. The reference harness supplies character constants and +string validation, rejects extra imports and cwd use, and does not emulate Node's +error messages or per-drive environment. Browser tests execute the actual Monaco +POSIX/Windows functions, compare those results, check platform alias selection, +and retain the existing Error/ERR_INVALID_ARG_TYPE behavior. + +```sh +yarn verify:monaco-node-path --fetch +yarn verify:monaco-node-path +yarn build:site-notices +yarn check:site-notices +yarn test:browser test/browser/editor-path.spec.js test/browser/site-vendor.spec.js --workers=1 +``` + +Full original core TypeScript compilation, WinJS/Unicode/other embedded origins +and the overall SITE-07 acceptance remain separate. A path-module proof is not +an exhaustive claim about every Node API or the rest of Monaco. diff --git a/scripts/site-vendor/monaco/node-path.ts b/scripts/site-vendor/monaco/node-path.ts new file mode 100644 index 000000000..ac161a54d --- /dev/null +++ b/scripts/site-vendor/monaco/node-path.ts @@ -0,0 +1,93 @@ +import type { VendorManifest } from '../notices.ts' +import type { Archive, Member, Remote } from './archives.ts' +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import vm from 'node:vm' + +export interface PathProvenance { + archives: Archive[] + remotes: Remote[] + nodeSource: string + vscodeSource: string + lockSource: string + maps: (Member & { source: string })[] + outputs: { development: Member, signature: string, offset: number, target: { path: string, sha256: string }, editor: Member }[] + component: { name: string, version: string, tarball: string, assets: string[], notices: string[] } + attribution: { source: string, target: string, sha256: string } + legacyNotice: { source: string, target: string, sha256: string } + fixtures: { path: string, sha256: string } +} + +export interface PathCase { namespace: 'win32' | 'posix', method: string, args: unknown[], expected: unknown } + +// Evaluate the frozen Node module for valid deterministic path calls only. This +// harness does not emulate Node's error messages, host cwd or drive environment. +export function nodePathResults(source: string, cases: Omit[]): unknown[] { + const constants = { CHAR_UPPERCASE_A: 65, CHAR_LOWERCASE_A: 97, CHAR_UPPERCASE_Z: 90, CHAR_LOWERCASE_Z: 122, CHAR_DOT: 46, CHAR_FORWARD_SLASH: 47, CHAR_BACKWARD_SLASH: 92, CHAR_COLON: 58, CHAR_QUESTION_MARK: 63 } + const module = { exports: {} as Record unknown>> } + vm.runInNewContext(source, { + module, + process: { platform: 'linux', env: {}, cwd: () => { throw new Error('Non-deterministic path cwd') } }, + require: (name: string) => { + if (name === 'internal/constants') + return constants + if (name === 'internal/validators') + return { validateString: (value: unknown) => assert.equal(typeof value, 'string', 'Valid path fixture required') } + assert.equal(name, 'internal/errors', 'Unexpected Node path import') + return { codes: { ERR_INVALID_ARG_TYPE: Error } } + }, + }, { timeout: 1000 }) + return cases.map((entry) => { + assert(['posix', 'win32'].includes(entry.namespace)) + assert(['normalize', 'basename', 'dirname', 'extname', 'join', 'relative', 'resolve', 'parse', 'format', 'isAbsolute', 'toNamespacedPath'].includes(entry.method), 'Unexpected path fixture method') + return JSON.parse(JSON.stringify(module.exports[entry.namespace]![entry.method]!(...entry.args))) + }) +} + +export function readPathProvenance(root: string): PathProvenance { + return JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/monaco-node-path-provenance.json'), 'utf8')) +} + +export function preparePathSource(source: string): string { + for (const name of ['isAbsolute', 'join', 'format', 'parse', 'toNamespacedPath', 'delimiter']) { + const line = `export const ${name} = (process.platform === 'win32' ? win32.${name} : posix.${name});\n` + assert.equal(source.split(line).length, 2, `Missing or repeated unused path export: ${name}`) + source = source.replace(line, '') + } + return source +} + +export function nodePathLicense(source: string): string { + const boundary = source.indexOf('\n\'use strict\';') + assert(boundary > 0, 'Missing Node source license boundary') + const lines = source.slice(0, boundary).trimEnd().split('\n') + assert(lines.every(line => line.startsWith('//')), 'Unexpected Node license comment') + const license = lines.map(line => line.replace(/^\/\/ ?/, '')).join('\n') + assert(license.startsWith('Copyright Joyent, Inc. and other Node contributors.')) + assert(license.endsWith('USE OR OTHER DEALINGS IN THE SOFTWARE.')) + return license +} + +export function verifyMonacoPathNotices(root: string, manifest: VendorManifest): void { + const record = readPathProvenance(root) + const group = manifest.groups.find(group => group.name === 'monaco-editor') + assert(group, 'Missing Monaco path group') + assert.equal(record.component.name, 'nodejs path (Monaco core)') + assert.equal(record.component.version, '14.16.0') + assert.deepEqual(record.component.assets, ['docs/assets/js/vs/editor/editor.main.js', 'docs/assets/js/vs/base/worker/workerMain.js'], 'Wrong Node path assets') + assert.deepEqual(record.component.notices, [record.legacyNotice.target, record.attribution.target], 'Missing Node path terms or source explanation') + assert.deepEqual(group.components?.filter(component => component.name === record.component.name), [record.component], 'Wrong Node path notice binding') + for (const notice of [record.legacyNotice, record.attribution]) + assert.deepEqual(group.notices.filter(item => item.target === notice.target), [notice], 'Missing Node path notice') + const license = nodePathLicense(fs.readFileSync(path.join(root, record.nodeSource), 'utf8')) + const legacy = fs.readFileSync(path.join(root, record.legacyNotice.source), 'utf8').replace(/\r\n/g, '\n') + assert(legacy.includes(license), 'Legacy notice omits actual Node path terms') + const port = fs.readFileSync(path.join(root, record.vscodeSource), 'utf8') + const start = port.indexOf('/**\n * Copyright Joyent, Inc. and other Node contributors.') + assert(start >= 0, 'Missing ported Node copyright') + const end = port.indexOf('\n */', start) + assert(end > start, 'Missing ported Node license end') + assert.equal(port.slice(start + 4, end).split('\n').map(line => line.replace(/^ \* ?/, '')).join('\n'), license, 'Ported Node license differs') + assert(port.includes('https://github.com/nodejs/node/blob/v14.16.0/lib/path.js'), 'Wrong ported Node version') +} diff --git a/scripts/site-vendor/monaco/reproduce-node-path.ts b/scripts/site-vendor/monaco/reproduce-node-path.ts new file mode 100644 index 000000000..245abc404 --- /dev/null +++ b/scripts/site-vendor/monaco/reproduce-node-path.ts @@ -0,0 +1,56 @@ +import type { PathCase } from './node-path.ts' +import assert from 'node:assert/strict' +import fs from 'node:fs' +import { createRequire } from 'node:module' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { ArchiveCache, hash } from './archives.ts' +import { nodePathResults, preparePathSource, readPathProvenance, verifyMonacoPathNotices } from './node-path.ts' + +const root = fileURLToPath(new URL('../../../', import.meta.url)) +assert(process.argv.slice(2).every(arg => arg === '--fetch'), 'Use reproduce-node-path.ts [--fetch]') +assert.equal(process.version, `v${fs.readFileSync(path.join(root, '.node-version'), 'utf8').trim()}`, 'Use canonical Node') +const record = readPathProvenance(root) +const cache = new ArchiveCache(root, path.join(root, 'refactor/.cache/monaco-review')) +await cache.verify(record.archives, record.remotes, process.argv.includes('--fetch')) +verifyMonacoPathNotices(root, JSON.parse(fs.readFileSync(path.join(root, 'scripts/site-vendor/manifest.json'), 'utf8'))) +const compiler = record.archives.find(archive => archive.name === 'typescript') +assert(compiler && compiler.version === '4.5.0-dev.20211021', 'Wrong path compiler') +const lock = fs.readFileSync(path.join(root, record.lockSource), 'utf8') +assert(lock.includes(`typescript@^4.5.0-dev.20211021:\n version "4.5.0-dev.20211021"`) && lock.includes(`integrity ${compiler.integrity}`), 'Path compiler not in upstream lock') +const isolated = new ArchiveCache(root, path.join(cache.directory, 'core-compiler')) +const archiveFile = `${compiler.name}-${compiler.version}.tgz` +fs.copyFileSync(path.join(cache.directory, archiveFile), path.join(isolated.directory, archiveFile)) +isolated.extractCompiler(compiler) +const require = createRequire(path.join(isolated.directory, 'compiler/entry.cjs')) +const ts = require('typescript') as typeof import('typescript') + +assert.equal(ts.version, compiler.version) +const prepared = preparePathSource(fs.readFileSync(path.join(root, record.vscodeSource), 'utf8')) +assert.equal(record.maps.length, 2) +for (const member of record.maps) { + const map: { sources: string[], sourcesContent: string[] } = JSON.parse(cache.member(member).toString('utf8')) + assert.equal(map.sources.filter(source => source === member.source).length, 1, 'Missing or repeated mapped path source') + assert.equal(map.sourcesContent[map.sources.indexOf(member.source)], prepared, 'Prepared path source differs') +} +const emitted = ts.transpileModule(prepared, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.AMD, newLine: ts.NewLineKind.LineFeed } }).outputText +const anonymous = 'define(["require", "exports", "vs/base/common/process"],' +assert.equal(emitted.split(anonymous).length, 2, 'Unexpected path emission signature') +assert.equal(record.outputs.length, 2) +const outputs = record.outputs.map((output) => { + assert(/^define\(__m\[\d+\/\*vs\/base\/common\/path\*\/\], __M\(\[0\/\*require\*\/,1\/\*exports\*\/,\d+\/\*vs\/base\/common\/process\*\/\]\),$/.test(output.signature), 'Unexpected path bundle signature') + const fragment = emitted.replace(anonymous, output.signature).trimEnd() + const development = cache.member(output.development).toString('utf8') + assert.equal(development.indexOf(fragment), output.offset, 'Compiled path module differs') + assert(output.offset > 0 && !development.includes(fragment, output.offset + 1), 'Missing or repeated compiled path module') + const target = fs.readFileSync(path.join(root, output.target.path)) + assert.equal(hash(target), output.target.sha256, 'Shipped path-bearing asset changed') + assert.deepEqual(target, cache.member(output.editor), 'Shipped path-bearing asset differs from archive') + return { path: output.target.path, offset: output.offset, moduleBytes: new TextEncoder().encode(fragment).length, exactModule: true, sha256: hash(target) } +}) +const fixtureBytes = fs.readFileSync(path.join(root, record.fixtures.path)) +assert.equal(hash(fixtureBytes), record.fixtures.sha256, 'Node path fixtures changed') +const fixtures: PathCase[] = JSON.parse(fixtureBytes.toString('utf8')) +assert.deepEqual(nodePathResults(fs.readFileSync(path.join(root, record.nodeSource), 'utf8'), fixtures), fixtures.map(fixture => fixture.expected), 'Original Node path results differ') +console.log(JSON.stringify({ archives: record.archives.length, gitSources: record.remotes.length, maps: record.maps.length, removedUnusedExports: 6, outputs, fixtures: fixtures.length, existingLicenseTextMatches: true, compiler: ts.version, fullCoreTypeScriptBuild: false, unmodifiedNodeRuntimeClaimed: false })) diff --git a/test/browser/editor-path.spec.js b/test/browser/editor-path.spec.js new file mode 100644 index 000000000..bd593d795 --- /dev/null +++ b/test/browser/editor-path.spec.js @@ -0,0 +1,31 @@ +import fs from 'node:fs' +import { expect, test } from './fixtures.js' + +const cases = JSON.parse(fs.readFileSync('refactor/baselines/monaco-node-path-fixtures.json', 'utf8')) + +test('docs Monaco path port preserves frozen Node path results in both namespaces', async ({ page }, testInfo) => { + await page.goto('/test/player.html?core=candidate&chapter=published') + await page.addScriptTag({ url: '/assets/js/vs/loader.js' }) + const result = await page.evaluate(async (cases) => { + window.require.config({ paths: { vs: '/assets/js/vs' } }) + const load = ids => new Promise((resolve, reject) => window.require(ids, (...modules) => resolve(modules), reject)) + await load(['vs/editor/editor.main']) + const [paths, process] = await load(['vs/base/common/path', 'vs/base/common/process']) + const values = cases.map(entry => paths[entry.namespace][entry.method](...entry.args)) + const namespace = process.platform === 'win32' ? 'win32' : 'posix' + const sample = namespace === 'win32' ? 'C:\\a\\..\\b' : '/a/../b' + let invalid + try { + paths.normalize(42) + } + catch (error) { + invalid = { name: error.name, code: error.code } + } + return { values, namespace, alias: paths.normalize(sample) === paths[namespace].normalize(sample), invalid } + }, cases) + expect(result.values).toEqual(cases.map(entry => entry.expected)) + expect(result.alias).toBe(true) + // This is the retained VS Code Error subclass, not Node's TypeError wrapper. + expect(result.invalid).toEqual({ name: 'Error', code: 'ERR_INVALID_ARG_TYPE' }) + await testInfo.attach('monaco-path-results', { contentType: 'application/json', body: JSON.stringify({ cases: cases.length, ...result }) }) +}) diff --git a/test/browser/site-vendor.spec.js b/test/browser/site-vendor.spec.js index bacafe490..4bc3f16ef 100644 --- a/test/browser/site-vendor.spec.js +++ b/test/browser/site-vendor.spec.js @@ -25,6 +25,7 @@ test('mobile vConsole shows logs and upstream site notice texts are served uncha { path: '/licenses/console/react-pure-render/ATTRIBUTION.md', count: 2 }, { path: '/licenses/monaco-editor/language-services/ATTRIBUTION.md', count: 12 }, { path: '/licenses/monaco-editor/core-origins/ATTRIBUTION.md', count: 3 }, + { path: '/licenses/monaco-editor/core-path/ATTRIBUTION.md', count: 1 }, ]) { const attribution = await request.get(path) expect(attribution.status()).toBe(200) diff --git a/test/monaco-provenance.test.js b/test/monaco-provenance.test.js index dacba2708..44de79ec6 100644 --- a/test/monaco-provenance.test.js +++ b/test/monaco-provenance.test.js @@ -9,6 +9,7 @@ import { inventoryCoreMap, readCoreNls, restoreCoreMapComment } from '../scripts import { adaptCoreOrigin } from '../scripts/site-vendor/monaco/core-origins.ts' import { coreStyleOrder, prepareCoreStyles } from '../scripts/site-vendor/monaco/css.ts' import { aliasModule, moduleIds, nameModule, verifyWorkerSources } from '../scripts/site-vendor/monaco/languages.ts' +import { nodePathResults, preparePathSource } from '../scripts/site-vendor/monaco/node-path.ts' import { browserTypeScript, verifyTypeScriptSource } from '../scripts/site-vendor/monaco/typescript.ts' const contributionGroups = ['typescript', 'css', 'json', 'html', 'languages'].map((name) => { @@ -16,6 +17,18 @@ const contributionGroups = ['typescript', 'css', 'json', 'html', 'languages'].ma return { name: `monaco-${name}`, modulePrefix, contrib: `${modulePrefix}/monaco.contribution` } }) +test('Path source preparation removes only the six explicit unused top-level exports', () => { + const source = ['isAbsolute', 'join', 'format', 'parse', 'toNamespacedPath', 'delimiter'].map(name => `export const ${name} = (process.platform === 'win32' ? win32.${name} : posix.${name});\n`).join('') + assert.equal(preparePathSource(`${source}export const retained = 1;\n`), 'export const retained = 1;\n') + assert.throws(() => preparePathSource(source + source), /repeated unused path export/) + assert.throws(() => preparePathSource(source.replace('win32.join', 'win32.other')), /unused path export: join/) +}) + +test('Node reference path fixtures reject filesystem imports and environment-dependent cwd', () => { + assert.throws(() => nodePathResults('require("fs")', []), /Unexpected Node path import/) + assert.throws(() => nodePathResults('process.cwd()', []), /Non-deterministic path cwd/) +}) + test('Core CSS inventory follows registration order instead of the module-name table order', () => { const source = 'var names = ["vs/css!vs/editor/editor.main","vs/css!vs/a","vs/css!vs/b"]; define(names[2],[],{}); define(names[1],[],{});' assert.deepEqual(coreStyleOrder(source), ['vs/b', 'vs/a']) diff --git a/test/site-notices.test.js b/test/site-notices.test.js index a7f10c163..b65b1e7c6 100644 --- a/test/site-notices.test.js +++ b/test/site-notices.test.js @@ -9,11 +9,23 @@ import process from 'node:process' import test from 'node:test' import { verifyConsoleNoticeSources } from '../scripts/site-vendor/console/notices.ts' import { verifyMonacoCoreNotices } from '../scripts/site-vendor/monaco/core-origins.ts' +import { verifyMonacoPathNotices } from '../scripts/site-vendor/monaco/node-path.ts' import { verifyMonacoLanguageNoticeArchives, verifyMonacoLanguageNotices } from '../scripts/site-vendor/monaco/notices.ts' import { generateNotices, writeOrCheckNotices } from '../scripts/site-vendor/notices.ts' const hash = bytes => createHash('sha256').update(bytes).digest('hex') +test('Node path attribution covers both editor and worker and keeps the original terms', () => { + const original = JSON.parse(fs.readFileSync('scripts/site-vendor/manifest.json', 'utf8')) + assert.doesNotThrow(() => verifyMonacoPathNotices(process.cwd(), original)) + const missingWorker = structuredClone(original) + missingWorker.groups.find(group => group.name === 'monaco-editor').components.find(component => component.name === 'nodejs path (Monaco core)').assets.pop() + assert.throws(() => verifyMonacoPathNotices(process.cwd(), missingWorker), /Node path notice binding/) + const missingTerms = structuredClone(original) + missingTerms.groups.find(group => group.name === 'monaco-editor').notices = missingTerms.groups.find(group => group.name === 'monaco-editor').notices.filter(notice => !notice.target.endsWith('/ThirdPartyNotices.txt')) + assert.throws(() => verifyMonacoPathNotices(process.cwd(), missingTerms), /Missing Node path notice/) +}) + test('Monaco core notices cannot replace DOMPurify terms with the Markdown parser license', () => { const original = JSON.parse(fs.readFileSync('scripts/site-vendor/manifest.json', 'utf8')) assert.doesNotThrow(() => verifyMonacoCoreNotices(process.cwd(), original))