@cyclonedx/cdxgen 12.1.4 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
|
@@ -0,0 +1,2247 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { describe, it } from "poku";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
cleanupTempDir,
|
|
16
|
+
collectInstalledExtensions,
|
|
17
|
+
extractExtensionCapabilities,
|
|
18
|
+
getIdeExtensionDirs,
|
|
19
|
+
parseExtensionDependencies,
|
|
20
|
+
parseExtensionDirName,
|
|
21
|
+
parseInstalledExtensionDir,
|
|
22
|
+
parseVsixManifest,
|
|
23
|
+
parseVsixPackageJson,
|
|
24
|
+
toComponent,
|
|
25
|
+
VSCODE_EXTENSION_PURL_TYPE,
|
|
26
|
+
} from "./vsixutils.js";
|
|
27
|
+
|
|
28
|
+
const baseTempDir = mkdtempSync(join(tmpdir(), "cdxgen-vsix-poku-"));
|
|
29
|
+
process.on("exit", () => {
|
|
30
|
+
try {
|
|
31
|
+
rmSync(baseTempDir, { recursive: true, force: true });
|
|
32
|
+
} catch (_e) {
|
|
33
|
+
// Ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("VSCODE_EXTENSION_PURL_TYPE", () => {
|
|
38
|
+
it("should be vscode-extension", () => {
|
|
39
|
+
assert.strictEqual(VSCODE_EXTENSION_PURL_TYPE, "vscode-extension");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("getIdeExtensionDirs", () => {
|
|
44
|
+
it("should return an array of IDE configurations", () => {
|
|
45
|
+
const ides = getIdeExtensionDirs();
|
|
46
|
+
assert.ok(Array.isArray(ides));
|
|
47
|
+
assert.ok(ides.length > 0);
|
|
48
|
+
for (const ide of ides) {
|
|
49
|
+
assert.ok(ide.name, "Each IDE should have a name");
|
|
50
|
+
assert.ok(Array.isArray(ide.dirs), "Each IDE should have dirs array");
|
|
51
|
+
assert.ok(ide.dirs.length > 0, "Each IDE should have at least one dir");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should include well-known IDEs", () => {
|
|
56
|
+
const ides = getIdeExtensionDirs();
|
|
57
|
+
const names = ides.map((ide) => ide.name);
|
|
58
|
+
assert.ok(names.includes("VS Code"), "Should include VS Code");
|
|
59
|
+
assert.ok(
|
|
60
|
+
names.includes("VS Code Insiders"),
|
|
61
|
+
"Should include VS Code Insiders",
|
|
62
|
+
);
|
|
63
|
+
assert.ok(names.includes("VSCodium"), "Should include VSCodium");
|
|
64
|
+
assert.ok(names.includes("Cursor"), "Should include Cursor");
|
|
65
|
+
assert.ok(names.includes("Windsurf"), "Should include Windsurf");
|
|
66
|
+
assert.ok(names.includes("Positron"), "Should include Positron");
|
|
67
|
+
assert.ok(names.includes("Theia"), "Should include Theia");
|
|
68
|
+
assert.ok(names.includes("code-server"), "Should include code-server");
|
|
69
|
+
assert.ok(names.includes("Trae"), "Should include Trae");
|
|
70
|
+
assert.ok(names.includes("Augment Code"), "Should include Augment Code");
|
|
71
|
+
assert.ok(
|
|
72
|
+
names.includes("VS Code Remote"),
|
|
73
|
+
"Should include VS Code Remote",
|
|
74
|
+
);
|
|
75
|
+
assert.ok(
|
|
76
|
+
names.includes("OpenVSCode Server"),
|
|
77
|
+
"Should include OpenVSCode Server",
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("parseVsixManifest", () => {
|
|
83
|
+
it("should return undefined for empty input", () => {
|
|
84
|
+
assert.strictEqual(parseVsixManifest(""), undefined);
|
|
85
|
+
assert.strictEqual(parseVsixManifest(null), undefined);
|
|
86
|
+
assert.strictEqual(parseVsixManifest(undefined), undefined);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should parse a valid vsixmanifest XML", () => {
|
|
90
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
91
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
|
|
92
|
+
<Metadata>
|
|
93
|
+
<Identity Id="python" Version="2023.25.0" Publisher="ms-python" TargetPlatform="linux-x64" />
|
|
94
|
+
<DisplayName>Python</DisplayName>
|
|
95
|
+
<Description>Python language support</Description>
|
|
96
|
+
</Metadata>
|
|
97
|
+
</PackageManifest>`;
|
|
98
|
+
const result = parseVsixManifest(xml);
|
|
99
|
+
assert.ok(result);
|
|
100
|
+
assert.strictEqual(result.publisher, "ms-python");
|
|
101
|
+
assert.strictEqual(result.name, "python");
|
|
102
|
+
assert.strictEqual(result.version, "2023.25.0");
|
|
103
|
+
assert.strictEqual(result.displayName, "Python");
|
|
104
|
+
assert.strictEqual(result.description, "Python language support");
|
|
105
|
+
assert.strictEqual(result.platform, "linux-x64");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should handle manifest without TargetPlatform", () => {
|
|
109
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
110
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
|
|
111
|
+
<Metadata>
|
|
112
|
+
<Identity Id="csharp" Version="2.15.30" Publisher="muhammad-sammy" />
|
|
113
|
+
<DisplayName>C#</DisplayName>
|
|
114
|
+
<Description>C# language support</Description>
|
|
115
|
+
</Metadata>
|
|
116
|
+
</PackageManifest>`;
|
|
117
|
+
const result = parseVsixManifest(xml);
|
|
118
|
+
assert.ok(result);
|
|
119
|
+
assert.strictEqual(result.publisher, "muhammad-sammy");
|
|
120
|
+
assert.strictEqual(result.name, "csharp");
|
|
121
|
+
assert.strictEqual(result.version, "2.15.30");
|
|
122
|
+
assert.strictEqual(result.platform, "");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should handle manifest without Description or DisplayName", () => {
|
|
126
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
127
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
|
|
128
|
+
<Metadata>
|
|
129
|
+
<Identity Id="myext" Version="1.0.0" Publisher="testpub" />
|
|
130
|
+
</Metadata>
|
|
131
|
+
</PackageManifest>`;
|
|
132
|
+
const result = parseVsixManifest(xml);
|
|
133
|
+
assert.ok(result);
|
|
134
|
+
assert.strictEqual(result.publisher, "testpub");
|
|
135
|
+
assert.strictEqual(result.name, "myext");
|
|
136
|
+
assert.strictEqual(result.version, "1.0.0");
|
|
137
|
+
assert.strictEqual(result.displayName, "");
|
|
138
|
+
assert.strictEqual(result.description, "");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should handle larger manifest with tags", () => {
|
|
142
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
143
|
+
<PackageManifest Version="2.0.0"
|
|
144
|
+
xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011"
|
|
145
|
+
xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
|
|
146
|
+
|
|
147
|
+
<Metadata>
|
|
148
|
+
<Identity Id="MyCompany.MyExtension"
|
|
149
|
+
Version="1.2.3"
|
|
150
|
+
Language="en-US"
|
|
151
|
+
Publisher="My Company" />
|
|
152
|
+
<DisplayName>My Awesome Extension</DisplayName>
|
|
153
|
+
<Description xml:space="preserve">A description of what this extension does.</Description>
|
|
154
|
+
<MoreInfo>https://github.com/mycompany/myextension</MoreInfo>
|
|
155
|
+
<License>LICENSE.txt</License>
|
|
156
|
+
<Icon>Resources\\icon.png</Icon>
|
|
157
|
+
<PreviewImage>Resources\\preview.png</PreviewImage>
|
|
158
|
+
<Tags>productivity, coding, tools</Tags>
|
|
159
|
+
</Metadata>
|
|
160
|
+
|
|
161
|
+
<Installation InstalledByMsi="false" AllUsers="false">
|
|
162
|
+
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0,)" />
|
|
163
|
+
<InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[17.0,)" />
|
|
164
|
+
<InstallationTarget Id="Microsoft.VisualStudio.Enterprise" Version="[17.0,)" />
|
|
165
|
+
</Installation>
|
|
166
|
+
|
|
167
|
+
<Dependencies>
|
|
168
|
+
<Dependency Id="Microsoft.Framework.NDP"
|
|
169
|
+
DisplayName=".NET Framework"
|
|
170
|
+
d:Source="Manual"
|
|
171
|
+
Version="[4.8,)" />
|
|
172
|
+
</Dependencies>
|
|
173
|
+
|
|
174
|
+
<Prerequisites>
|
|
175
|
+
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor"
|
|
176
|
+
Version="[17.0,)"
|
|
177
|
+
DisplayName="Visual Studio core editor" />
|
|
178
|
+
</Prerequisites>
|
|
179
|
+
|
|
180
|
+
<Assets>
|
|
181
|
+
<Asset Type="Microsoft.VisualStudio.VsPackage"
|
|
182
|
+
d:Source="Project"
|
|
183
|
+
d:ProjectName="%CurrentProject%"
|
|
184
|
+
Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
|
|
185
|
+
<Asset Type="Microsoft.VisualStudio.MefComponent"
|
|
186
|
+
d:Source="Project"
|
|
187
|
+
d:ProjectName="%CurrentProject%"
|
|
188
|
+
Path="|%CurrentProject%|" />
|
|
189
|
+
</Assets>
|
|
190
|
+
</PackageManifest>`;
|
|
191
|
+
const result = parseVsixManifest(xml);
|
|
192
|
+
assert.ok(result);
|
|
193
|
+
assert.strictEqual(result.publisher, "My Company");
|
|
194
|
+
assert.strictEqual(result.name, "MyCompany.MyExtension");
|
|
195
|
+
assert.strictEqual(result.version, "1.2.3");
|
|
196
|
+
assert.strictEqual(result.displayName, "My Awesome Extension");
|
|
197
|
+
assert.strictEqual(
|
|
198
|
+
result.description,
|
|
199
|
+
"A description of what this extension does.",
|
|
200
|
+
);
|
|
201
|
+
assert.deepStrictEqual(result.tags, ["productivity", "coding", "tools"]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should parse a real one with tags", () => {
|
|
205
|
+
const xml = `
|
|
206
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
207
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
|
|
208
|
+
<Metadata>
|
|
209
|
+
<Identity Language="en-US" Id="volar" Version="3.2.6" Publisher="Vue" />
|
|
210
|
+
<DisplayName>Vue (Official)</DisplayName>
|
|
211
|
+
<Description xml:space="preserve">Language Support for Vue</Description>
|
|
212
|
+
<Tags>json,vue,__ext_vue,markdown,html,jade,__web_extension,__sponsor_extension</Tags>
|
|
213
|
+
<Categories>Programming Languages</Categories>
|
|
214
|
+
<GalleryFlags>Public</GalleryFlags>
|
|
215
|
+
|
|
216
|
+
<Properties>
|
|
217
|
+
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="^1.88.0" />
|
|
218
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="" />
|
|
219
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
|
|
220
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="workspace,web" />
|
|
221
|
+
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="" />
|
|
222
|
+
<Property Id="Microsoft.VisualStudio.Code.EnabledApiProposals" Value="" />
|
|
223
|
+
|
|
224
|
+
<Property Id="Microsoft.VisualStudio.Code.ExecutesCode" Value="true" />
|
|
225
|
+
<Property Id="Microsoft.VisualStudio.Code.SponsorLink" Value="https://github.com/sponsors/johnsoncodehk" />
|
|
226
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="https://github.com/vuejs/language-tools.git" />
|
|
227
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="https://github.com/vuejs/language-tools.git" />
|
|
228
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.GitHub" Value="https://github.com/vuejs/language-tools.git" />
|
|
229
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="https://github.com/vuejs/language-tools/issues" />
|
|
230
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="https://github.com/vuejs/language-tools#readme" />
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="true" />
|
|
234
|
+
<Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="Free"/>
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
</Properties>
|
|
239
|
+
<License>extension/LICENSE.txt</License>
|
|
240
|
+
<Icon>extension/icon.png</Icon>
|
|
241
|
+
</Metadata>
|
|
242
|
+
<Installation>
|
|
243
|
+
<InstallationTarget Id="Microsoft.VisualStudio.Code"/>
|
|
244
|
+
</Installation>
|
|
245
|
+
<Dependencies/>
|
|
246
|
+
<Assets>
|
|
247
|
+
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
|
|
248
|
+
<Asset Type="Microsoft.VisualStudio.Services.Content.Details" Path="extension/readme.md" Addressable="true" />
|
|
249
|
+
<Asset Type="Microsoft.VisualStudio.Services.Content.Changelog" Path="extension/changelog.md" Addressable="true" />
|
|
250
|
+
<Asset Type="Microsoft.VisualStudio.Services.Content.License" Path="extension/LICENSE.txt" Addressable="true" />
|
|
251
|
+
<Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="extension/icon.png" Addressable="true" />
|
|
252
|
+
</Assets>
|
|
253
|
+
</PackageManifest>
|
|
254
|
+
`;
|
|
255
|
+
const result = parseVsixManifest(xml);
|
|
256
|
+
assert.ok(result);
|
|
257
|
+
assert.strictEqual(result.publisher, "Vue");
|
|
258
|
+
assert.strictEqual(result.name, "volar");
|
|
259
|
+
assert.strictEqual(result.version, "3.2.6");
|
|
260
|
+
assert.strictEqual(result.displayName, "Vue (Official)");
|
|
261
|
+
assert.strictEqual(result.description, "Language Support for Vue");
|
|
262
|
+
assert.deepStrictEqual(result.tags, [
|
|
263
|
+
"json",
|
|
264
|
+
"vue",
|
|
265
|
+
"__ext_vue",
|
|
266
|
+
"markdown",
|
|
267
|
+
"html",
|
|
268
|
+
"jade",
|
|
269
|
+
"__web_extension",
|
|
270
|
+
"__sponsor_extension",
|
|
271
|
+
]);
|
|
272
|
+
});
|
|
273
|
+
it("should parse a real one with properties", () => {
|
|
274
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
275
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
|
|
276
|
+
<Metadata>
|
|
277
|
+
<Identity Language="en-US" Id="pyrefly" Version="0.61.0" Publisher="meta" TargetPlatform="win32-x64"/>
|
|
278
|
+
<DisplayName>Pyrefly - Python Language Tooling</DisplayName>
|
|
279
|
+
<Description xml:space="preserve">Python autocomplete, typechecking, code navigation and more! Powered by Pyrefly, an open-source language server</Description>
|
|
280
|
+
<Tags>multi-root ready,python,type,typecheck,typehint,completion,lint,Python,__ext_py,__ext_pyi</Tags>
|
|
281
|
+
<Categories>Programming Languages,Linters,Other</Categories>
|
|
282
|
+
<GalleryFlags>Public</GalleryFlags>
|
|
283
|
+
|
|
284
|
+
<Properties>
|
|
285
|
+
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="^1.94.0" />
|
|
286
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="ms-python.python" />
|
|
287
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
|
|
288
|
+
<Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="workspace" />
|
|
289
|
+
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="" />
|
|
290
|
+
<Property Id="Microsoft.VisualStudio.Code.EnabledApiProposals" Value="" />
|
|
291
|
+
|
|
292
|
+
<Property Id="Microsoft.VisualStudio.Code.ExecutesCode" Value="true" />
|
|
293
|
+
|
|
294
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="https://github.com/facebook/pyrefly.git" />
|
|
295
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="https://github.com/facebook/pyrefly.git" />
|
|
296
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.GitHub" Value="https://github.com/facebook/pyrefly.git" />
|
|
297
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="https://github.com/facebook/pyrefly/issues" />
|
|
298
|
+
<Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="https://github.com/facebook/pyrefly#readme" />
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="true" />
|
|
302
|
+
<Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="Free"/>
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
</Properties>
|
|
307
|
+
<License>extension/LICENSE.txt</License>
|
|
308
|
+
<Icon>extension/images/pyrefly-symbol.png</Icon>
|
|
309
|
+
</Metadata>
|
|
310
|
+
<Installation>
|
|
311
|
+
<InstallationTarget Id="Microsoft.VisualStudio.Code"/>
|
|
312
|
+
</Installation>
|
|
313
|
+
<Dependencies/>
|
|
314
|
+
<Assets>
|
|
315
|
+
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
|
|
316
|
+
<Asset Type="Microsoft.VisualStudio.Services.Content.Details" Path="extension/README.md" Addressable="true" />
|
|
317
|
+
<Asset Type="Microsoft.VisualStudio.Services.Content.License" Path="extension/LICENSE.txt" Addressable="true" />
|
|
318
|
+
<Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="extension/images/pyrefly-symbol.png" Addressable="true" />
|
|
319
|
+
</Assets>
|
|
320
|
+
</PackageManifest>`;
|
|
321
|
+
const result = parseVsixManifest(xml);
|
|
322
|
+
assert.ok(result);
|
|
323
|
+
assert.strictEqual(result.publisher, "meta");
|
|
324
|
+
assert.strictEqual(result.name, "pyrefly");
|
|
325
|
+
assert.strictEqual(result.version, "0.61.0");
|
|
326
|
+
assert.strictEqual(result.platform, "win32-x64");
|
|
327
|
+
assert.strictEqual(result.displayName, "Pyrefly - Python Language Tooling");
|
|
328
|
+
// Properties tag parsing
|
|
329
|
+
assert.strictEqual(result.vscodeEngine, "^1.94.0");
|
|
330
|
+
assert.deepStrictEqual(result.extensionDependencies, ["ms-python.python"]);
|
|
331
|
+
assert.deepStrictEqual(result.extensionKind, ["workspace"]);
|
|
332
|
+
assert.strictEqual(result.executesCode, true);
|
|
333
|
+
// Links from Properties
|
|
334
|
+
assert.ok(result.links);
|
|
335
|
+
assert.strictEqual(
|
|
336
|
+
result.links.Source,
|
|
337
|
+
"https://github.com/facebook/pyrefly.git",
|
|
338
|
+
);
|
|
339
|
+
assert.strictEqual(
|
|
340
|
+
result.links.GitHub,
|
|
341
|
+
"https://github.com/facebook/pyrefly.git",
|
|
342
|
+
);
|
|
343
|
+
assert.strictEqual(
|
|
344
|
+
result.links.Support,
|
|
345
|
+
"https://github.com/facebook/pyrefly/issues",
|
|
346
|
+
);
|
|
347
|
+
assert.strictEqual(
|
|
348
|
+
result.links.Learn,
|
|
349
|
+
"https://github.com/facebook/pyrefly#readme",
|
|
350
|
+
);
|
|
351
|
+
// Empty ExtensionPack should not be set
|
|
352
|
+
assert.strictEqual(result.extensionPack, undefined);
|
|
353
|
+
});
|
|
354
|
+
it("should return undefined for invalid XML", () => {
|
|
355
|
+
const result = parseVsixManifest("not xml at all");
|
|
356
|
+
assert.strictEqual(result, undefined);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should return undefined for XML without PackageManifest", () => {
|
|
360
|
+
const xml = `<?xml version="1.0" encoding="utf-8"?><root><child /></root>`;
|
|
361
|
+
const result = parseVsixManifest(xml);
|
|
362
|
+
assert.strictEqual(result, undefined);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("extractExtensionCapabilities", () => {
|
|
367
|
+
it("should return empty object for null/undefined", () => {
|
|
368
|
+
assert.deepStrictEqual(extractExtensionCapabilities(null), {});
|
|
369
|
+
assert.deepStrictEqual(extractExtensionCapabilities(undefined), {});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should extract activation events", () => {
|
|
373
|
+
const pkg = {
|
|
374
|
+
activationEvents: ["onLanguage:python", "onCommand:python.runLinting"],
|
|
375
|
+
};
|
|
376
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
377
|
+
assert.deepStrictEqual(caps.activationEvents, [
|
|
378
|
+
"onLanguage:python",
|
|
379
|
+
"onCommand:python.runLinting",
|
|
380
|
+
]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should flag wildcard activation (always-on extension)", () => {
|
|
384
|
+
const pkg = { activationEvents: ["*"] };
|
|
385
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
386
|
+
assert.deepStrictEqual(caps.activationEvents, ["*"]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("should extract extensionKind", () => {
|
|
390
|
+
const pkg = { extensionKind: ["workspace"] };
|
|
391
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
392
|
+
assert.deepStrictEqual(caps.extensionKind, ["workspace"]);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should extract extensionDependencies", () => {
|
|
396
|
+
const pkg = {
|
|
397
|
+
extensionDependencies: ["ms-python.python", "ms-toolsai.jupyter"],
|
|
398
|
+
};
|
|
399
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
400
|
+
assert.deepStrictEqual(caps.extensionDependencies, [
|
|
401
|
+
"ms-python.python",
|
|
402
|
+
"ms-toolsai.jupyter",
|
|
403
|
+
]);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should extract extensionPack", () => {
|
|
407
|
+
const pkg = {
|
|
408
|
+
extensionPack: [
|
|
409
|
+
"ms-python.python",
|
|
410
|
+
"ms-python.vscode-pylance",
|
|
411
|
+
"ms-toolsai.jupyter",
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
415
|
+
assert.deepStrictEqual(caps.extensionPack, [
|
|
416
|
+
"ms-python.python",
|
|
417
|
+
"ms-python.vscode-pylance",
|
|
418
|
+
"ms-toolsai.jupyter",
|
|
419
|
+
]);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("should extract workspace trust configuration", () => {
|
|
423
|
+
const pkg = {
|
|
424
|
+
capabilities: {
|
|
425
|
+
untrustedWorkspaces: {
|
|
426
|
+
supported: "limited",
|
|
427
|
+
description: "Only basic",
|
|
428
|
+
},
|
|
429
|
+
virtualWorkspaces: { supported: false },
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
433
|
+
assert.deepStrictEqual(caps.untrustedWorkspaces, {
|
|
434
|
+
supported: "limited",
|
|
435
|
+
description: "Only basic",
|
|
436
|
+
});
|
|
437
|
+
assert.deepStrictEqual(caps.virtualWorkspaces, { supported: false });
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("should extract contributed features", () => {
|
|
441
|
+
const pkg = {
|
|
442
|
+
contributes: {
|
|
443
|
+
commands: [{ command: "ext.run", title: "Run" }],
|
|
444
|
+
debuggers: [{ type: "python", label: "Python" }],
|
|
445
|
+
terminal: [{ id: "ext.terminal" }],
|
|
446
|
+
authentication: [{ id: "ext.auth", label: "My Auth" }],
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
450
|
+
assert.ok(caps.contributes.includes("commands:count:1"));
|
|
451
|
+
assert.ok(caps.contributes.includes("debuggers:count:1"));
|
|
452
|
+
assert.ok(caps.contributes.includes("terminal-access"));
|
|
453
|
+
assert.ok(caps.contributes.includes("authentication-provider"));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("should extract main and browser entry points", () => {
|
|
457
|
+
const pkg = {
|
|
458
|
+
main: "./dist/extension.js",
|
|
459
|
+
browser: "./dist/web/extension.js",
|
|
460
|
+
};
|
|
461
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
462
|
+
assert.strictEqual(caps.main, "./dist/extension.js");
|
|
463
|
+
assert.strictEqual(caps.browser, "./dist/web/extension.js");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("should detect lifecycle scripts", () => {
|
|
467
|
+
const pkg = {
|
|
468
|
+
scripts: {
|
|
469
|
+
postinstall: "node setup.js",
|
|
470
|
+
"vscode:prepublish": "npm run build",
|
|
471
|
+
"vscode:uninstall": "node cleanup.js",
|
|
472
|
+
test: "jest",
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
476
|
+
assert.ok(caps.lifecycleScripts.includes("postinstall"));
|
|
477
|
+
assert.ok(caps.lifecycleScripts.includes("vscode:prepublish"));
|
|
478
|
+
assert.ok(caps.lifecycleScripts.includes("vscode:uninstall"));
|
|
479
|
+
assert.ok(
|
|
480
|
+
!caps.lifecycleScripts.includes("test"),
|
|
481
|
+
"test is not a lifecycle script",
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("should handle extension with taskDefinitions", () => {
|
|
486
|
+
const pkg = {
|
|
487
|
+
contributes: {
|
|
488
|
+
taskDefinitions: [{ type: "npm" }],
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
492
|
+
assert.ok(caps.contributes.includes("terminal-access"));
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("should handle extension with filesystem providers", () => {
|
|
496
|
+
const pkg = {
|
|
497
|
+
contributes: {
|
|
498
|
+
fileSystemProviders: [{ scheme: "ftp", authority: "ftp" }],
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
502
|
+
assert.ok(caps.contributes.includes("filesystem-provider"));
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("should return empty for extension with no capabilities", () => {
|
|
506
|
+
const pkg = { name: "simple-ext", version: "1.0.0" };
|
|
507
|
+
const caps = extractExtensionCapabilities(pkg);
|
|
508
|
+
assert.ok(!caps.activationEvents);
|
|
509
|
+
assert.ok(!caps.contributes);
|
|
510
|
+
assert.ok(!caps.lifecycleScripts);
|
|
511
|
+
assert.ok(!caps.main);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe("parseVsixPackageJson", () => {
|
|
516
|
+
it("should return undefined for empty input", () => {
|
|
517
|
+
assert.strictEqual(parseVsixPackageJson(""), undefined);
|
|
518
|
+
assert.strictEqual(parseVsixPackageJson("{}"), undefined);
|
|
519
|
+
assert.strictEqual(parseVsixPackageJson(null), undefined);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("should parse a valid package.json string", () => {
|
|
523
|
+
const json = JSON.stringify({
|
|
524
|
+
name: "python",
|
|
525
|
+
publisher: "ms-python",
|
|
526
|
+
version: "2023.25.0",
|
|
527
|
+
displayName: "Python",
|
|
528
|
+
description: "Python language support with Pylance",
|
|
529
|
+
});
|
|
530
|
+
const result = parseVsixPackageJson(json, "/test/path");
|
|
531
|
+
assert.ok(result);
|
|
532
|
+
assert.strictEqual(result.publisher, "ms-python");
|
|
533
|
+
assert.strictEqual(result.name, "python");
|
|
534
|
+
assert.strictEqual(result.version, "2023.25.0");
|
|
535
|
+
assert.strictEqual(result.displayName, "Python");
|
|
536
|
+
assert.strictEqual(
|
|
537
|
+
result.description,
|
|
538
|
+
"Python language support with Pylance",
|
|
539
|
+
);
|
|
540
|
+
assert.strictEqual(result.srcPath, "/test/path");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("should parse a pre-parsed object", () => {
|
|
544
|
+
const obj = {
|
|
545
|
+
name: "go",
|
|
546
|
+
publisher: "golang",
|
|
547
|
+
version: "0.39.1",
|
|
548
|
+
displayName: "Go",
|
|
549
|
+
};
|
|
550
|
+
const result = parseVsixPackageJson(obj);
|
|
551
|
+
assert.ok(result);
|
|
552
|
+
assert.strictEqual(result.publisher, "golang");
|
|
553
|
+
assert.strictEqual(result.name, "go");
|
|
554
|
+
assert.strictEqual(result.version, "0.39.1");
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("should include capabilities from package.json", () => {
|
|
558
|
+
const obj = {
|
|
559
|
+
name: "python",
|
|
560
|
+
publisher: "ms-python",
|
|
561
|
+
version: "1.0.0",
|
|
562
|
+
activationEvents: ["onLanguage:python"],
|
|
563
|
+
main: "./dist/extension.js",
|
|
564
|
+
contributes: {
|
|
565
|
+
commands: [{ command: "python.run", title: "Run" }],
|
|
566
|
+
},
|
|
567
|
+
scripts: {
|
|
568
|
+
postinstall: "node install.js",
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
const result = parseVsixPackageJson(obj);
|
|
572
|
+
assert.ok(result);
|
|
573
|
+
assert.ok(result.capabilities);
|
|
574
|
+
assert.deepStrictEqual(result.capabilities.activationEvents, [
|
|
575
|
+
"onLanguage:python",
|
|
576
|
+
]);
|
|
577
|
+
assert.strictEqual(result.capabilities.main, "./dist/extension.js");
|
|
578
|
+
assert.ok(result.capabilities.contributes.includes("commands:count:1"));
|
|
579
|
+
assert.ok(result.capabilities.lifecycleScripts.includes("postinstall"));
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should handle missing optional fields", () => {
|
|
583
|
+
const obj = { name: "simple-ext" };
|
|
584
|
+
const result = parseVsixPackageJson(obj);
|
|
585
|
+
assert.ok(result);
|
|
586
|
+
assert.strictEqual(result.name, "simple-ext");
|
|
587
|
+
assert.strictEqual(result.publisher, "");
|
|
588
|
+
assert.strictEqual(result.version, "");
|
|
589
|
+
assert.strictEqual(result.displayName, "");
|
|
590
|
+
assert.strictEqual(result.description, "");
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("should return undefined for invalid JSON string", () => {
|
|
594
|
+
const result = parseVsixPackageJson("not json");
|
|
595
|
+
assert.strictEqual(result, undefined);
|
|
596
|
+
});
|
|
597
|
+
it("should handle a real one", () => {
|
|
598
|
+
const result = parseVsixPackageJson(`
|
|
599
|
+
{
|
|
600
|
+
"private": true,
|
|
601
|
+
"name": "volar",
|
|
602
|
+
"version": "3.2.6",
|
|
603
|
+
"repository": {
|
|
604
|
+
"type": "git",
|
|
605
|
+
"url": "https://github.com/vuejs/language-tools.git",
|
|
606
|
+
"directory": "extensions/vscode"
|
|
607
|
+
},
|
|
608
|
+
"categories": [
|
|
609
|
+
"Programming Languages"
|
|
610
|
+
],
|
|
611
|
+
"sponsor": {
|
|
612
|
+
"url": "https://github.com/sponsors/johnsoncodehk"
|
|
613
|
+
},
|
|
614
|
+
"icon": "icon.png",
|
|
615
|
+
"displayName": "Vue (Official)",
|
|
616
|
+
"description": "Language Support for Vue",
|
|
617
|
+
"author": "johnsoncodehk",
|
|
618
|
+
"publisher": "Vue",
|
|
619
|
+
"engines": {
|
|
620
|
+
"vscode": "^1.88.0"
|
|
621
|
+
},
|
|
622
|
+
"activationEvents": [
|
|
623
|
+
"onLanguage"
|
|
624
|
+
],
|
|
625
|
+
"main": "./main.js",
|
|
626
|
+
"browser": "./web.js",
|
|
627
|
+
"capabilities": {
|
|
628
|
+
"virtualWorkspaces": {
|
|
629
|
+
"supported": "limited",
|
|
630
|
+
"description": "Install https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-web to have IntelliSense for .vue files in Web IDE."
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
"contributes": {
|
|
634
|
+
"jsonValidation": [
|
|
635
|
+
{
|
|
636
|
+
"fileMatch": [
|
|
637
|
+
"tsconfig.json",
|
|
638
|
+
"tsconfig.*.json",
|
|
639
|
+
"tsconfig-*.json",
|
|
640
|
+
"jsconfig.json",
|
|
641
|
+
"jsconfig.*.json",
|
|
642
|
+
"jsconfig-*.json"
|
|
643
|
+
],
|
|
644
|
+
"url": "./schemas/vue-tsconfig.schema.json"
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
"languages": [
|
|
648
|
+
{
|
|
649
|
+
"id": "vue",
|
|
650
|
+
"extensions": [
|
|
651
|
+
".vue"
|
|
652
|
+
],
|
|
653
|
+
"configuration": "./languages/vue-language-configuration.json"
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"id": "markdown",
|
|
657
|
+
"configuration": "./languages/markdown-language-configuration.json"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"id": "html",
|
|
661
|
+
"configuration": "./languages/sfc-template-language-configuration.json"
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"id": "jade",
|
|
665
|
+
"configuration": "./languages/sfc-template-language-configuration.json"
|
|
666
|
+
}
|
|
667
|
+
],
|
|
668
|
+
"grammars": [
|
|
669
|
+
{
|
|
670
|
+
"language": "vue",
|
|
671
|
+
"scopeName": "text.html.vue",
|
|
672
|
+
"path": "./syntaxes/vue.tmLanguage.json",
|
|
673
|
+
"embeddedLanguages": {
|
|
674
|
+
"text.html.vue": "vue",
|
|
675
|
+
"text": "plaintext",
|
|
676
|
+
"text.html.derivative": "html",
|
|
677
|
+
"text.html.markdown": "markdown",
|
|
678
|
+
"text.pug": "jade",
|
|
679
|
+
"source.css": "css",
|
|
680
|
+
"source.css.scss": "scss",
|
|
681
|
+
"source.css.less": "less",
|
|
682
|
+
"source.sass": "sass",
|
|
683
|
+
"source.stylus": "stylus",
|
|
684
|
+
"source.postcss": "postcss",
|
|
685
|
+
"source.js": "javascript",
|
|
686
|
+
"source.ts": "typescript",
|
|
687
|
+
"source.js.jsx": "javascriptreact",
|
|
688
|
+
"source.tsx": "typescriptreact",
|
|
689
|
+
"source.coffee": "coffeescript",
|
|
690
|
+
"meta.tag.js": "jsx-tags",
|
|
691
|
+
"meta.tag.tsx": "jsx-tags",
|
|
692
|
+
"meta.tag.without-attributes.js": "jsx-tags",
|
|
693
|
+
"meta.tag.without-attributes.tsx": "jsx-tags",
|
|
694
|
+
"source.json": "json",
|
|
695
|
+
"source.json.comments": "jsonc",
|
|
696
|
+
"source.json5": "json5",
|
|
697
|
+
"source.yaml": "yaml",
|
|
698
|
+
"source.toml": "toml",
|
|
699
|
+
"source.graphql": "graphql"
|
|
700
|
+
},
|
|
701
|
+
"unbalancedBracketScopes": [
|
|
702
|
+
"keyword.operator.relational",
|
|
703
|
+
"storage.type.function.arrow",
|
|
704
|
+
"keyword.operator.bitwise.shift",
|
|
705
|
+
"meta.brace.angle",
|
|
706
|
+
"punctuation.definition.tag"
|
|
707
|
+
]
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"scopeName": "markdown.vue.codeblock",
|
|
711
|
+
"path": "./syntaxes/markdown-vue.json",
|
|
712
|
+
"injectTo": [
|
|
713
|
+
"text.html.markdown"
|
|
714
|
+
],
|
|
715
|
+
"embeddedLanguages": {
|
|
716
|
+
"meta.embedded.block.vue": "vue",
|
|
717
|
+
"text.html.vue": "vue",
|
|
718
|
+
"text": "plaintext",
|
|
719
|
+
"text.html.derivative": "html",
|
|
720
|
+
"text.html.markdown": "markdown",
|
|
721
|
+
"text.pug": "jade",
|
|
722
|
+
"source.css": "css",
|
|
723
|
+
"source.css.scss": "scss",
|
|
724
|
+
"source.css.less": "less",
|
|
725
|
+
"source.sass": "sass",
|
|
726
|
+
"source.stylus": "stylus",
|
|
727
|
+
"source.postcss": "postcss",
|
|
728
|
+
"source.js": "javascript",
|
|
729
|
+
"source.ts": "typescript",
|
|
730
|
+
"source.js.jsx": "javascriptreact",
|
|
731
|
+
"source.tsx": "typescriptreact",
|
|
732
|
+
"source.coffee": "coffeescript",
|
|
733
|
+
"meta.tag.js": "jsx-tags",
|
|
734
|
+
"meta.tag.tsx": "jsx-tags",
|
|
735
|
+
"meta.tag.without-attributes.js": "jsx-tags",
|
|
736
|
+
"meta.tag.without-attributes.tsx": "jsx-tags",
|
|
737
|
+
"source.json": "json",
|
|
738
|
+
"source.json.comments": "jsonc",
|
|
739
|
+
"source.json5": "json5",
|
|
740
|
+
"source.yaml": "yaml",
|
|
741
|
+
"source.toml": "toml",
|
|
742
|
+
"source.graphql": "graphql"
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"scopeName": "mdx.vue.codeblock",
|
|
747
|
+
"path": "./syntaxes/mdx-vue.json",
|
|
748
|
+
"injectTo": [
|
|
749
|
+
"source.mdx"
|
|
750
|
+
],
|
|
751
|
+
"embeddedLanguages": {
|
|
752
|
+
"mdx.embedded.vue": "vue",
|
|
753
|
+
"text.html.vue": "vue",
|
|
754
|
+
"text": "plaintext",
|
|
755
|
+
"text.html.derivative": "html",
|
|
756
|
+
"text.html.markdown": "markdown",
|
|
757
|
+
"text.pug": "jade",
|
|
758
|
+
"source.css": "css",
|
|
759
|
+
"source.css.scss": "scss",
|
|
760
|
+
"source.css.less": "less",
|
|
761
|
+
"source.sass": "sass",
|
|
762
|
+
"source.stylus": "stylus",
|
|
763
|
+
"source.postcss": "postcss",
|
|
764
|
+
"source.js": "javascript",
|
|
765
|
+
"source.ts": "typescript",
|
|
766
|
+
"source.js.jsx": "javascriptreact",
|
|
767
|
+
"source.tsx": "typescriptreact",
|
|
768
|
+
"source.coffee": "coffeescript",
|
|
769
|
+
"meta.tag.js": "jsx-tags",
|
|
770
|
+
"meta.tag.tsx": "jsx-tags",
|
|
771
|
+
"meta.tag.without-attributes.js": "jsx-tags",
|
|
772
|
+
"meta.tag.without-attributes.tsx": "jsx-tags",
|
|
773
|
+
"source.json": "json",
|
|
774
|
+
"source.json.comments": "jsonc",
|
|
775
|
+
"source.json5": "json5",
|
|
776
|
+
"source.yaml": "yaml",
|
|
777
|
+
"source.toml": "toml",
|
|
778
|
+
"source.graphql": "graphql"
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"scopeName": "vue.directives",
|
|
783
|
+
"path": "./syntaxes/vue-directives.json",
|
|
784
|
+
"injectTo": [
|
|
785
|
+
"text.html.vue",
|
|
786
|
+
"text.html.markdown",
|
|
787
|
+
"text.html.derivative",
|
|
788
|
+
"text.pug"
|
|
789
|
+
]
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
"scopeName": "vue.interpolations",
|
|
793
|
+
"path": "./syntaxes/vue-interpolations.json",
|
|
794
|
+
"injectTo": [
|
|
795
|
+
"text.html.vue",
|
|
796
|
+
"text.html.markdown",
|
|
797
|
+
"text.html.derivative",
|
|
798
|
+
"text.pug"
|
|
799
|
+
]
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"scopeName": "vue.sfc.script.leading-operator-fix",
|
|
803
|
+
"path": "./syntaxes/vue-sfc-script-leading-operator-fix.json",
|
|
804
|
+
"injectTo": [
|
|
805
|
+
"text.html.vue"
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"scopeName": "vue.sfc.style.variable.injection",
|
|
810
|
+
"path": "./syntaxes/vue-sfc-style-variable-injection.json",
|
|
811
|
+
"injectTo": [
|
|
812
|
+
"text.html.vue"
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
],
|
|
816
|
+
"semanticTokenScopes": [
|
|
817
|
+
{
|
|
818
|
+
"language": "vue",
|
|
819
|
+
"scopes": {
|
|
820
|
+
"component": [
|
|
821
|
+
"support.class.component.vue",
|
|
822
|
+
"entity.name.type.class.vue"
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
"language": "markdown",
|
|
828
|
+
"scopes": {
|
|
829
|
+
"component": [
|
|
830
|
+
"support.class.component.vue",
|
|
831
|
+
"entity.name.type.class.vue"
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
"language": "html",
|
|
837
|
+
"scopes": {
|
|
838
|
+
"component": [
|
|
839
|
+
"support.class.component.vue",
|
|
840
|
+
"entity.name.type.class.vue"
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
],
|
|
845
|
+
"breakpoints": [
|
|
846
|
+
{
|
|
847
|
+
"language": "vue"
|
|
848
|
+
}
|
|
849
|
+
],
|
|
850
|
+
"configuration": {
|
|
851
|
+
"type": "object",
|
|
852
|
+
"title": "Vue",
|
|
853
|
+
"properties": {
|
|
854
|
+
"vue.trace.server": {
|
|
855
|
+
"scope": "window",
|
|
856
|
+
"type": "string",
|
|
857
|
+
"enum": [
|
|
858
|
+
"off",
|
|
859
|
+
"messages",
|
|
860
|
+
"verbose"
|
|
861
|
+
],
|
|
862
|
+
"default": "off",
|
|
863
|
+
"markdownDescription": "%configuration.trace.server%"
|
|
864
|
+
},
|
|
865
|
+
"vue.editor.focusMode": {
|
|
866
|
+
"type": "boolean",
|
|
867
|
+
"default": false,
|
|
868
|
+
"markdownDescription": "%configuration.editor.focusMode%"
|
|
869
|
+
},
|
|
870
|
+
"vue.editor.reactivityVisualization": {
|
|
871
|
+
"type": "boolean",
|
|
872
|
+
"default": true,
|
|
873
|
+
"markdownDescription": "%configuration.editor.reactivityVisualization%"
|
|
874
|
+
},
|
|
875
|
+
"vue.editor.templateInterpolationDecorators": {
|
|
876
|
+
"type": "boolean",
|
|
877
|
+
"default": true,
|
|
878
|
+
"markdownDescription": "%configuration.editor.templateInterpolationDecorators%"
|
|
879
|
+
},
|
|
880
|
+
"vue.server.path": {
|
|
881
|
+
"type": "string",
|
|
882
|
+
"markdownDescription": "%configuration.server.path%"
|
|
883
|
+
},
|
|
884
|
+
"vue.server.includeLanguages": {
|
|
885
|
+
"type": "array",
|
|
886
|
+
"items": {
|
|
887
|
+
"type": "string"
|
|
888
|
+
},
|
|
889
|
+
"default": [
|
|
890
|
+
"vue"
|
|
891
|
+
],
|
|
892
|
+
"markdownDescription": "%configuration.server.includeLanguages%"
|
|
893
|
+
},
|
|
894
|
+
"vue.codeActions.askNewComponentName": {
|
|
895
|
+
"type": "boolean",
|
|
896
|
+
"default": true,
|
|
897
|
+
"markdownDescription": "%configuration.codeActions.askNewComponentName%"
|
|
898
|
+
},
|
|
899
|
+
"vue.hover.rich": {
|
|
900
|
+
"type": "boolean",
|
|
901
|
+
"default": false,
|
|
902
|
+
"markdownDescription": "%configuration.hover.rich%"
|
|
903
|
+
},
|
|
904
|
+
"vue.suggest.componentNameCasing": {
|
|
905
|
+
"type": "string",
|
|
906
|
+
"enum": [
|
|
907
|
+
"preferKebabCase",
|
|
908
|
+
"preferPascalCase",
|
|
909
|
+
"alwaysKebabCase",
|
|
910
|
+
"alwaysPascalCase"
|
|
911
|
+
],
|
|
912
|
+
"enumDescriptions": [
|
|
913
|
+
"Prefer kebab-case (lowercase with hyphens, e.g. my-component)",
|
|
914
|
+
"Prefer PascalCase (UpperCamelCase, e.g. MyComponent)",
|
|
915
|
+
"Always kebab-case (enforce kebab-case, e.g. my-component)",
|
|
916
|
+
"Always PascalCase (enforce PascalCase, e.g. MyComponent)"
|
|
917
|
+
],
|
|
918
|
+
"default": "preferPascalCase",
|
|
919
|
+
"markdownDescription": "%configuration.suggest.componentNameCasing%"
|
|
920
|
+
},
|
|
921
|
+
"vue.suggest.propNameCasing": {
|
|
922
|
+
"type": "string",
|
|
923
|
+
"enum": [
|
|
924
|
+
"preferKebabCase",
|
|
925
|
+
"preferCamelCase",
|
|
926
|
+
"alwaysKebabCase",
|
|
927
|
+
"alwaysCamelCase"
|
|
928
|
+
],
|
|
929
|
+
"enumDescriptions": [
|
|
930
|
+
"Prefer kebab-case (lowercase with hyphens, e.g. my-prop)",
|
|
931
|
+
"Prefer camelCase (lowerCamelCase, e.g. myProp)",
|
|
932
|
+
"Always kebab-case (enforce kebab-case, e.g. my-prop)",
|
|
933
|
+
"Always camelCase (enforce camelCase, e.g. myProp)"
|
|
934
|
+
],
|
|
935
|
+
"default": "preferKebabCase",
|
|
936
|
+
"markdownDescription": "%configuration.suggest.propNameCasing%"
|
|
937
|
+
},
|
|
938
|
+
"vue.suggest.defineAssignment": {
|
|
939
|
+
"type": "boolean",
|
|
940
|
+
"default": true,
|
|
941
|
+
"markdownDescription": "%configuration.suggest.defineAssignment%"
|
|
942
|
+
},
|
|
943
|
+
"vue.autoInsert.dotValue": {
|
|
944
|
+
"type": "boolean",
|
|
945
|
+
"default": false,
|
|
946
|
+
"markdownDescription": "%configuration.autoInsert.dotValue%"
|
|
947
|
+
},
|
|
948
|
+
"vue.autoInsert.bracketSpacing": {
|
|
949
|
+
"type": "boolean",
|
|
950
|
+
"default": true,
|
|
951
|
+
"markdownDescription": "%configuration.autoInsert.bracketSpacing%"
|
|
952
|
+
},
|
|
953
|
+
"vue.inlayHints.destructuredProps": {
|
|
954
|
+
"type": "boolean",
|
|
955
|
+
"default": false,
|
|
956
|
+
"markdownDescription": "%configuration.inlayHints.destructuredProps%"
|
|
957
|
+
},
|
|
958
|
+
"vue.inlayHints.missingProps": {
|
|
959
|
+
"type": "boolean",
|
|
960
|
+
"default": false,
|
|
961
|
+
"markdownDescription": "%configuration.inlayHints.missingProps%"
|
|
962
|
+
},
|
|
963
|
+
"vue.inlayHints.inlineHandlerLeading": {
|
|
964
|
+
"type": "boolean",
|
|
965
|
+
"default": false,
|
|
966
|
+
"markdownDescription": "%configuration.inlayHints.inlineHandlerLeading%"
|
|
967
|
+
},
|
|
968
|
+
"vue.inlayHints.optionsWrapper": {
|
|
969
|
+
"type": "boolean",
|
|
970
|
+
"default": false,
|
|
971
|
+
"markdownDescription": "%configuration.inlayHints.optionsWrapper%"
|
|
972
|
+
},
|
|
973
|
+
"vue.inlayHints.vBindShorthand": {
|
|
974
|
+
"type": "boolean",
|
|
975
|
+
"default": false,
|
|
976
|
+
"markdownDescription": "%configuration.inlayHints.vBindShorthand%"
|
|
977
|
+
},
|
|
978
|
+
"vue.format.template.initialIndent": {
|
|
979
|
+
"type": "boolean",
|
|
980
|
+
"default": true,
|
|
981
|
+
"markdownDescription": "%configuration.format.template.initialIndent%"
|
|
982
|
+
},
|
|
983
|
+
"vue.format.script.initialIndent": {
|
|
984
|
+
"type": "boolean",
|
|
985
|
+
"default": false,
|
|
986
|
+
"markdownDescription": "%configuration.format.script.initialIndent%"
|
|
987
|
+
},
|
|
988
|
+
"vue.format.style.initialIndent": {
|
|
989
|
+
"type": "boolean",
|
|
990
|
+
"default": false,
|
|
991
|
+
"markdownDescription": "%configuration.format.style.initialIndent%"
|
|
992
|
+
},
|
|
993
|
+
"vue.format.script.enabled": {
|
|
994
|
+
"type": "boolean",
|
|
995
|
+
"default": true,
|
|
996
|
+
"markdownDescription": "%configuration.format.script.enabled%"
|
|
997
|
+
},
|
|
998
|
+
"vue.format.template.enabled": {
|
|
999
|
+
"type": "boolean",
|
|
1000
|
+
"default": true,
|
|
1001
|
+
"markdownDescription": "%configuration.format.template.enabled%"
|
|
1002
|
+
},
|
|
1003
|
+
"vue.format.style.enabled": {
|
|
1004
|
+
"type": "boolean",
|
|
1005
|
+
"default": true,
|
|
1006
|
+
"markdownDescription": "%configuration.format.style.enabled%"
|
|
1007
|
+
},
|
|
1008
|
+
"vue.format.wrapAttributes": {
|
|
1009
|
+
"type": "string",
|
|
1010
|
+
"default": "auto",
|
|
1011
|
+
"enum": [
|
|
1012
|
+
"auto",
|
|
1013
|
+
"force",
|
|
1014
|
+
"force-aligned",
|
|
1015
|
+
"force-expand-multiline",
|
|
1016
|
+
"aligned-multiple",
|
|
1017
|
+
"preserve",
|
|
1018
|
+
"preserve-aligned"
|
|
1019
|
+
],
|
|
1020
|
+
"markdownDescription": "%configuration.format.wrapAttributes%"
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
"commands": [
|
|
1025
|
+
{
|
|
1026
|
+
"command": "vue.welcome",
|
|
1027
|
+
"title": "%command.welcome%",
|
|
1028
|
+
"category": "Vue"
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
"command": "vue.action.restartServer",
|
|
1032
|
+
"title": "%command.action.restartServer%",
|
|
1033
|
+
"category": "Vue"
|
|
1034
|
+
}
|
|
1035
|
+
],
|
|
1036
|
+
"menus": {
|
|
1037
|
+
"editor/context": [
|
|
1038
|
+
{
|
|
1039
|
+
"command": "typescript.goToSourceDefinition",
|
|
1040
|
+
"when": "tsSupportsSourceDefinition && resourceLangId == vue",
|
|
1041
|
+
"group": "navigation@9"
|
|
1042
|
+
}
|
|
1043
|
+
],
|
|
1044
|
+
"explorer/context": [
|
|
1045
|
+
{
|
|
1046
|
+
"command": "typescript.findAllFileReferences",
|
|
1047
|
+
"when": "tsSupportsFileReferences && resourceLangId == vue",
|
|
1048
|
+
"group": "4_search"
|
|
1049
|
+
}
|
|
1050
|
+
],
|
|
1051
|
+
"editor/title/context": [
|
|
1052
|
+
{
|
|
1053
|
+
"command": "typescript.findAllFileReferences",
|
|
1054
|
+
"when": "tsSupportsFileReferences && resourceLangId == vue"
|
|
1055
|
+
}
|
|
1056
|
+
],
|
|
1057
|
+
"commandPalette": [
|
|
1058
|
+
{
|
|
1059
|
+
"command": "typescript.reloadProjects",
|
|
1060
|
+
"when": "editorLangId == vue && typescript.isManagedFile"
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
"command": "typescript.goToProjectConfig",
|
|
1064
|
+
"when": "editorLangId == vue && typescript.isManagedFile"
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
"command": "typescript.sortImports",
|
|
1068
|
+
"when": "supportedCodeAction =~ /(\\\\s|^)source\\\\.sortImports\\\\b/ && editorLangId =~ /^vue$/"
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
"command": "typescript.removeUnusedImports",
|
|
1072
|
+
"when": "supportedCodeAction =~ /(\\\\s|^)source\\\\.removeUnusedImports\\\\b/ && editorLangId =~ /^vue$/"
|
|
1073
|
+
}
|
|
1074
|
+
]
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
"scripts": {
|
|
1078
|
+
"vscode:prepublish": "rolldown --config",
|
|
1079
|
+
"pack": "npx @vscode/vsce package",
|
|
1080
|
+
"gen-ext-meta": "vscode-ext-gen --scope vue --output src/generated-meta.ts && cd ../.. && npm run format"
|
|
1081
|
+
},
|
|
1082
|
+
"devDependencies": {
|
|
1083
|
+
"@types/node": "^22.10.4",
|
|
1084
|
+
"@types/vscode": "1.88.0",
|
|
1085
|
+
"@volar/typescript": "2.4.28",
|
|
1086
|
+
"@volar/vscode": "2.4.28",
|
|
1087
|
+
"@vue/language-core": "workspace:*",
|
|
1088
|
+
"@vue/language-server": "workspace:*",
|
|
1089
|
+
"@vue/typescript-plugin": "workspace:*",
|
|
1090
|
+
"laplacenoma": "latest",
|
|
1091
|
+
"reactive-vscode": "^0.4.1",
|
|
1092
|
+
"rolldown": "latest",
|
|
1093
|
+
"vscode-ext-gen": "latest",
|
|
1094
|
+
"vscode-tmlanguage-snapshot": "latest"
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
`);
|
|
1098
|
+
assert.ok(result);
|
|
1099
|
+
assert.strictEqual(result.publisher, "Vue");
|
|
1100
|
+
assert.strictEqual(result.name, "volar");
|
|
1101
|
+
assert.strictEqual(result.version, "3.2.6");
|
|
1102
|
+
assert.strictEqual(result.displayName, "Vue (Official)");
|
|
1103
|
+
assert.strictEqual(result.description, "Language Support for Vue");
|
|
1104
|
+
assert.strictEqual(result.platform, "");
|
|
1105
|
+
assert.strictEqual(result.srcPath, undefined);
|
|
1106
|
+
// Should now capture repository as external reference (was a bug before: checked packageJsonData instead of pkg)
|
|
1107
|
+
assert.ok(result.externalReferences);
|
|
1108
|
+
assert.deepStrictEqual(result.externalReferences, [
|
|
1109
|
+
{ type: "vcs", url: "https://github.com/vuejs/language-tools.git" },
|
|
1110
|
+
]);
|
|
1111
|
+
assert.deepStrictEqual(result.capabilities, {
|
|
1112
|
+
activationEvents: ["onLanguage"],
|
|
1113
|
+
virtualWorkspaces: {
|
|
1114
|
+
supported: "limited",
|
|
1115
|
+
description:
|
|
1116
|
+
"Install https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-web to have IntelliSense for .vue files in Web IDE.",
|
|
1117
|
+
},
|
|
1118
|
+
contributes: [
|
|
1119
|
+
"breakpoints:count:1",
|
|
1120
|
+
"commands:count:2",
|
|
1121
|
+
"language-server-plugins",
|
|
1122
|
+
],
|
|
1123
|
+
main: "./main.js",
|
|
1124
|
+
browser: "./web.js",
|
|
1125
|
+
lifecycleScripts: ["vscode:prepublish"],
|
|
1126
|
+
});
|
|
1127
|
+
// Should capture devDependencies for later analysis
|
|
1128
|
+
assert.ok(result.devDependencies);
|
|
1129
|
+
assert.strictEqual(result.devDependencies["@types/node"], "^22.10.4");
|
|
1130
|
+
assert.strictEqual(result.devDependencies["@types/vscode"], "1.88.0");
|
|
1131
|
+
assert.strictEqual(
|
|
1132
|
+
result.devDependencies["@vue/language-core"],
|
|
1133
|
+
"workspace:*",
|
|
1134
|
+
);
|
|
1135
|
+
});
|
|
1136
|
+
it("should handle another real one (incomplete)", () => {
|
|
1137
|
+
const result = parseVsixPackageJson(`
|
|
1138
|
+
{
|
|
1139
|
+
"name": "pyrefly",
|
|
1140
|
+
"displayName": "Pyrefly - Python Language Tooling",
|
|
1141
|
+
"description": "Python autocomplete, typechecking, code navigation and more! Powered by Pyrefly, an open-source language server",
|
|
1142
|
+
"icon": "images/pyrefly-symbol.png",
|
|
1143
|
+
"extensionKind": [
|
|
1144
|
+
"workspace"
|
|
1145
|
+
],
|
|
1146
|
+
"author": "Facebook",
|
|
1147
|
+
"license": "Apache2",
|
|
1148
|
+
"version": "0.61.0",
|
|
1149
|
+
"repository": {
|
|
1150
|
+
"type": "git",
|
|
1151
|
+
"url": "https://github.com/facebook/pyrefly"
|
|
1152
|
+
},
|
|
1153
|
+
"publisher": "meta",
|
|
1154
|
+
"categories": [
|
|
1155
|
+
"Programming Languages",
|
|
1156
|
+
"Linters",
|
|
1157
|
+
"Other"
|
|
1158
|
+
],
|
|
1159
|
+
"keywords": [
|
|
1160
|
+
"multi-root ready",
|
|
1161
|
+
"python",
|
|
1162
|
+
"type",
|
|
1163
|
+
"typecheck",
|
|
1164
|
+
"typehint",
|
|
1165
|
+
"completion",
|
|
1166
|
+
"lint"
|
|
1167
|
+
],
|
|
1168
|
+
"engines": {
|
|
1169
|
+
"vscode": "^1.94.0"
|
|
1170
|
+
},
|
|
1171
|
+
"main": "./dist/extension",
|
|
1172
|
+
"activationEvents": [
|
|
1173
|
+
"onLanguage:python",
|
|
1174
|
+
"onNotebook:jupyter-notebook"
|
|
1175
|
+
],
|
|
1176
|
+
"capabilities": {
|
|
1177
|
+
"untrustedWorkspaces": {
|
|
1178
|
+
"supported": false,
|
|
1179
|
+
"description": "Pyrefly can be configured to execute binaries. A malicious actor could exploit this to run arbitrary code on your machine."
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
"contributes": {
|
|
1183
|
+
"languages": [
|
|
1184
|
+
{
|
|
1185
|
+
"id": "python",
|
|
1186
|
+
"aliases": [
|
|
1187
|
+
"Python"
|
|
1188
|
+
],
|
|
1189
|
+
"extensions": [
|
|
1190
|
+
".py",
|
|
1191
|
+
".pyi"
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
],
|
|
1195
|
+
"commands": [
|
|
1196
|
+
{
|
|
1197
|
+
"title": "Restart Pyrefly Client",
|
|
1198
|
+
"category": "pyrefly",
|
|
1199
|
+
"command": "pyrefly.restartClient"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
"title": "Fold All Docstrings",
|
|
1203
|
+
"category": "pyrefly",
|
|
1204
|
+
"command": "pyrefly.foldAllDocstrings"
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
"title": "Unfold All Docstrings",
|
|
1208
|
+
"category": "pyrefly",
|
|
1209
|
+
"command": "pyrefly.unfoldAllDocstrings"
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
"title": "Run File",
|
|
1213
|
+
"category": "pyrefly",
|
|
1214
|
+
"command": "pyrefly.runMain"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
"title": "Run Test",
|
|
1218
|
+
"category": "pyrefly",
|
|
1219
|
+
"command": "pyrefly.runTest"
|
|
1220
|
+
}
|
|
1221
|
+
],
|
|
1222
|
+
"semanticTokenScopes": [
|
|
1223
|
+
{
|
|
1224
|
+
"language": "python",
|
|
1225
|
+
"scopes": {
|
|
1226
|
+
"variable.readonly": [
|
|
1227
|
+
"variable.other.constant.python"
|
|
1228
|
+
]
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
],
|
|
1232
|
+
"configurationDefaults": {
|
|
1233
|
+
"editor.semanticTokenColorCustomizations": {
|
|
1234
|
+
"rules": {
|
|
1235
|
+
"variable.readonly:python": "#4EC9B0"
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
"configuration": {
|
|
1240
|
+
"properties": {
|
|
1241
|
+
"pyrefly.lspPath": {
|
|
1242
|
+
"type": "string",
|
|
1243
|
+
"default": "",
|
|
1244
|
+
"description": "The path to the binary used for the lsp",
|
|
1245
|
+
"scope": "machine-overridable"
|
|
1246
|
+
},
|
|
1247
|
+
"pyrefly.lspArguments": {
|
|
1248
|
+
"type": "array",
|
|
1249
|
+
"items": {
|
|
1250
|
+
"type": "string"
|
|
1251
|
+
},
|
|
1252
|
+
"default": [
|
|
1253
|
+
"lsp"
|
|
1254
|
+
],
|
|
1255
|
+
"description": "Additional arguments that should be passed to the binary at pyrefly.lspPath",
|
|
1256
|
+
"scope": "machine-overridable"
|
|
1257
|
+
},
|
|
1258
|
+
"python.pyrefly.disableLanguageServices": {
|
|
1259
|
+
"type": "boolean",
|
|
1260
|
+
"default": false,
|
|
1261
|
+
"description": "If true, pyrefly will not provide other IDE services like completions, hover, definition, etc. To control type errors, see \`python.pyrefly.displayTypeErrors\`",
|
|
1262
|
+
"scope": "resource"
|
|
1263
|
+
},
|
|
1264
|
+
"python.pyrefly.displayTypeErrors": {
|
|
1265
|
+
"type": "string",
|
|
1266
|
+
"description": "If 'default', Pyrefly will only provide type check squiggles in the IDE if your file is covered by a Pyrefly configuration. If 'force-off', Pyrefly will never provide type check squiggles in the IDE. If 'force-on', Pyrefly will always provide type check squiggles in the IDE. If 'error-missing-imports', Pyrefly will only show errors for missing imports and missing sources (missing-import, missing-source, and missing-source-for-stubs).",
|
|
1267
|
+
"default": "default",
|
|
1268
|
+
"enum": [
|
|
1269
|
+
"default",
|
|
1270
|
+
"force-on",
|
|
1271
|
+
"force-off",
|
|
1272
|
+
"error-missing-imports"
|
|
1273
|
+
],
|
|
1274
|
+
"scope": "resource"
|
|
1275
|
+
},
|
|
1276
|
+
"pyrefly.trace.server": {
|
|
1277
|
+
"type": "string",
|
|
1278
|
+
"description": "Set to 'verbose' to enable LSP trace in the console",
|
|
1279
|
+
"default": "off",
|
|
1280
|
+
"enum": [
|
|
1281
|
+
"off",
|
|
1282
|
+
"verbose"
|
|
1283
|
+
]
|
|
1284
|
+
},
|
|
1285
|
+
"python.pyrefly.disabledLanguageServices": {
|
|
1286
|
+
"type": "object",
|
|
1287
|
+
"default": {},
|
|
1288
|
+
"description": "Disable specific language services. Set individual services to true to disable them.",
|
|
1289
|
+
"scope": "resource",
|
|
1290
|
+
"properties": {
|
|
1291
|
+
"hover": {
|
|
1292
|
+
"type": "boolean",
|
|
1293
|
+
"default": false
|
|
1294
|
+
},
|
|
1295
|
+
"documentSymbol": {
|
|
1296
|
+
"type": "boolean",
|
|
1297
|
+
"default": false
|
|
1298
|
+
},
|
|
1299
|
+
"workspaceSymbol": {
|
|
1300
|
+
"type": "boolean",
|
|
1301
|
+
"default": false
|
|
1302
|
+
},
|
|
1303
|
+
"inlayHint": {
|
|
1304
|
+
"type": "boolean",
|
|
1305
|
+
"default": false
|
|
1306
|
+
},
|
|
1307
|
+
"completion": {
|
|
1308
|
+
"type": "boolean",
|
|
1309
|
+
"default": false
|
|
1310
|
+
},
|
|
1311
|
+
"codeAction": {
|
|
1312
|
+
"type": "boolean",
|
|
1313
|
+
"default": false
|
|
1314
|
+
},
|
|
1315
|
+
"definition": {
|
|
1316
|
+
"type": "boolean",
|
|
1317
|
+
"default": false
|
|
1318
|
+
},
|
|
1319
|
+
"declaration": {
|
|
1320
|
+
"type": "boolean",
|
|
1321
|
+
"default": false
|
|
1322
|
+
},
|
|
1323
|
+
"typeDefinition": {
|
|
1324
|
+
"type": "boolean",
|
|
1325
|
+
"default": false
|
|
1326
|
+
},
|
|
1327
|
+
"references": {
|
|
1328
|
+
"type": "boolean",
|
|
1329
|
+
"default": false
|
|
1330
|
+
},
|
|
1331
|
+
"documentHighlight": {
|
|
1332
|
+
"type": "boolean",
|
|
1333
|
+
"default": false
|
|
1334
|
+
},
|
|
1335
|
+
"rename": {
|
|
1336
|
+
"type": "boolean",
|
|
1337
|
+
"default": false
|
|
1338
|
+
},
|
|
1339
|
+
"codeLens": {
|
|
1340
|
+
"type": "boolean",
|
|
1341
|
+
"default": false
|
|
1342
|
+
},
|
|
1343
|
+
"semanticTokens": {
|
|
1344
|
+
"type": "boolean",
|
|
1345
|
+
"default": false
|
|
1346
|
+
},
|
|
1347
|
+
"signatureHelp": {
|
|
1348
|
+
"type": "boolean",
|
|
1349
|
+
"default": false
|
|
1350
|
+
},
|
|
1351
|
+
"implementation": {
|
|
1352
|
+
"type": "boolean",
|
|
1353
|
+
"default": false
|
|
1354
|
+
},
|
|
1355
|
+
"callHierarchy": {
|
|
1356
|
+
"type": "boolean",
|
|
1357
|
+
"default": false,
|
|
1358
|
+
"description": "Disable call hierarchy feature (Show Incoming/Outgoing Calls)"
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
"python.analysis.showHoverGoToLinks": {
|
|
1363
|
+
"type": "boolean",
|
|
1364
|
+
"default": true,
|
|
1365
|
+
"description": "Controls whether hover tooltips include 'Go to definition' and 'Go to type definition' navigation links.",
|
|
1366
|
+
"scope": "resource"
|
|
1367
|
+
},
|
|
1368
|
+
"python.analysis.completeFunctionParens": {
|
|
1369
|
+
"type": "boolean",
|
|
1370
|
+
"default": false,
|
|
1371
|
+
"description": "Automatically insert parentheses when completing a function or method.",
|
|
1372
|
+
"scope": "resource"
|
|
1373
|
+
},
|
|
1374
|
+
"python.pyrefly.syncNotebooks": {
|
|
1375
|
+
"type": "boolean",
|
|
1376
|
+
"default": true,
|
|
1377
|
+
"description": "If true, Pyrefly will sync notebook documents with the language server. Set to false to disable notebook support."
|
|
1378
|
+
},
|
|
1379
|
+
"python.pyrefly.runnableCodeLens": {
|
|
1380
|
+
"type": "boolean",
|
|
1381
|
+
"default": false,
|
|
1382
|
+
"description": "Enable Pyrefly's Run/Test CodeLens actions for Python files.",
|
|
1383
|
+
"scope": "resource"
|
|
1384
|
+
},
|
|
1385
|
+
"python.pyrefly.streamDiagnostics": {
|
|
1386
|
+
"type": "boolean",
|
|
1387
|
+
"default": true,
|
|
1388
|
+
"description": "If true (default), Pyrefly will stream diagnostics as they become available during recheck, providing incremental feedback. Set to false to only publish diagnostics after the full recheck completes.",
|
|
1389
|
+
"scope": "resource"
|
|
1390
|
+
},
|
|
1391
|
+
"python.pyrefly.diagnosticMode": {
|
|
1392
|
+
"type": "string",
|
|
1393
|
+
"default": "openFilesOnly",
|
|
1394
|
+
"description": "Controls the scope of Pyrefly's diagnostic analysis. When set to 'openFilesOnly', diagnostics are only provided for files that are currently open in the editor. When set to 'workspace', diagnostics are computed and published for all files in the workspace.",
|
|
1395
|
+
"enum": [
|
|
1396
|
+
"openFilesOnly",
|
|
1397
|
+
"workspace"
|
|
1398
|
+
],
|
|
1399
|
+
"scope": "resource"
|
|
1400
|
+
},
|
|
1401
|
+
"pyrefly.commentFoldingRanges": {
|
|
1402
|
+
"type": "boolean",
|
|
1403
|
+
"default": false,
|
|
1404
|
+
"description": "Controls whether comment section folding ranges are included in the editor. When true, comments following the pattern '# Section Name ----' (with 4+ trailing dashes) create collapsible regions, similar to R's code section convention."
|
|
1405
|
+
},
|
|
1406
|
+
"python.pyrefly.configPath": {
|
|
1407
|
+
"type": "string",
|
|
1408
|
+
"default": "",
|
|
1409
|
+
"description": "Path to a pyrefly.toml or pyproject.toml configuration file. When set, the LSP will use this config for all files in your workspace instead of the default Pyrefly config-finding logic. Prefer to use default logic wherever possible.",
|
|
1410
|
+
"scope": "resource"
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
"scripts": {
|
|
1416
|
+
"compile": "npm run check-types && node esbuild.js",
|
|
1417
|
+
"check-types": "tsc --noEmit",
|
|
1418
|
+
"watch": "npm-run-all -p watch:*",
|
|
1419
|
+
"watch:esbuild": "node esbuild.js --watch",
|
|
1420
|
+
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
|
1421
|
+
"vscode:prepublish": "npm run package",
|
|
1422
|
+
"package": "npm run check-types && node esbuild.js --production",
|
|
1423
|
+
"test": "vscode-test"
|
|
1424
|
+
},
|
|
1425
|
+
"devDependencies": {
|
|
1426
|
+
"@types/mocha": "^10.0.10",
|
|
1427
|
+
"@types/node": "^16.11.7",
|
|
1428
|
+
"@types/vscode": "^1.78.1",
|
|
1429
|
+
"@vscode/test-cli": "^0.0.10",
|
|
1430
|
+
"@vscode/test-electron": "^2.5.2",
|
|
1431
|
+
"@vscode/vsce": "^2.9.2",
|
|
1432
|
+
"esbuild": "^0.25.1",
|
|
1433
|
+
"npm-run-all": "^4.1.5",
|
|
1434
|
+
"typescript": "^4.4.3"
|
|
1435
|
+
},
|
|
1436
|
+
"dependencies": {
|
|
1437
|
+
"@vscode/python-extension": "^1.0.5",
|
|
1438
|
+
"serialize-javascript": "^7.0.5",
|
|
1439
|
+
"underscore": "^1.13.8",
|
|
1440
|
+
"vsce": "^2.15.0",
|
|
1441
|
+
"vscode-languageclient": "9.0.1"
|
|
1442
|
+
},
|
|
1443
|
+
"extensionDependencies": [
|
|
1444
|
+
"ms-python.python"
|
|
1445
|
+
]
|
|
1446
|
+
}
|
|
1447
|
+
`);
|
|
1448
|
+
assert.ok(result);
|
|
1449
|
+
assert.strictEqual(result.publisher, "meta");
|
|
1450
|
+
assert.strictEqual(result.name, "pyrefly");
|
|
1451
|
+
assert.strictEqual(result.version, "0.61.0");
|
|
1452
|
+
assert.strictEqual(result.displayName, "Pyrefly - Python Language Tooling");
|
|
1453
|
+
// Should capture repository as external reference
|
|
1454
|
+
assert.ok(result.externalReferences);
|
|
1455
|
+
assert.deepStrictEqual(result.externalReferences, [
|
|
1456
|
+
{ type: "vcs", url: "https://github.com/facebook/pyrefly" },
|
|
1457
|
+
]);
|
|
1458
|
+
// Capabilities
|
|
1459
|
+
assert.ok(result.capabilities);
|
|
1460
|
+
assert.deepStrictEqual(result.capabilities.extensionKind, ["workspace"]);
|
|
1461
|
+
assert.deepStrictEqual(result.capabilities.activationEvents, [
|
|
1462
|
+
"onLanguage:python",
|
|
1463
|
+
"onNotebook:jupyter-notebook",
|
|
1464
|
+
]);
|
|
1465
|
+
assert.deepStrictEqual(result.capabilities.extensionDependencies, [
|
|
1466
|
+
"ms-python.python",
|
|
1467
|
+
]);
|
|
1468
|
+
assert.deepStrictEqual(result.capabilities.untrustedWorkspaces, {
|
|
1469
|
+
supported: false,
|
|
1470
|
+
description:
|
|
1471
|
+
"Pyrefly can be configured to execute binaries. A malicious actor could exploit this to run arbitrary code on your machine.",
|
|
1472
|
+
});
|
|
1473
|
+
assert.strictEqual(result.capabilities.main, "./dist/extension");
|
|
1474
|
+
assert.ok(result.capabilities.contributes.includes("commands:count:5"));
|
|
1475
|
+
assert.ok(
|
|
1476
|
+
result.capabilities.lifecycleScripts.includes("vscode:prepublish"),
|
|
1477
|
+
);
|
|
1478
|
+
// Dependencies
|
|
1479
|
+
assert.ok(result.dependencies);
|
|
1480
|
+
assert.strictEqual(
|
|
1481
|
+
result.dependencies["@vscode/python-extension"],
|
|
1482
|
+
"^1.0.5",
|
|
1483
|
+
);
|
|
1484
|
+
assert.strictEqual(result.dependencies["vscode-languageclient"], "9.0.1");
|
|
1485
|
+
assert.ok(result.devDependencies);
|
|
1486
|
+
assert.strictEqual(result.devDependencies["typescript"], "^4.4.3");
|
|
1487
|
+
assert.strictEqual(result.devDependencies["esbuild"], "^0.25.1");
|
|
1488
|
+
});
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
describe("parseExtensionDependencies", () => {
|
|
1492
|
+
it("should return empty arrays for package with no dependencies", () => {
|
|
1493
|
+
const pkg = { name: "simple-ext" };
|
|
1494
|
+
const result = parseExtensionDependencies(
|
|
1495
|
+
pkg,
|
|
1496
|
+
"pkg:vscode-extension/pub/simple-ext@1.0.0",
|
|
1497
|
+
);
|
|
1498
|
+
assert.deepStrictEqual(result.components, []);
|
|
1499
|
+
assert.deepStrictEqual(result.dependencies, []);
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
it("should parse dependencies with required scope", () => {
|
|
1503
|
+
const pkg = {
|
|
1504
|
+
name: "test-ext",
|
|
1505
|
+
dependencies: {
|
|
1506
|
+
lodash: "^4.17.21",
|
|
1507
|
+
axios: "1.6.0",
|
|
1508
|
+
},
|
|
1509
|
+
};
|
|
1510
|
+
const result = parseExtensionDependencies(
|
|
1511
|
+
pkg,
|
|
1512
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1513
|
+
);
|
|
1514
|
+
assert.strictEqual(result.components.length, 2);
|
|
1515
|
+
const lodashComp = result.components.find((c) => c.name === "lodash");
|
|
1516
|
+
assert.ok(lodashComp);
|
|
1517
|
+
assert.strictEqual(lodashComp.scope, "required");
|
|
1518
|
+
assert.strictEqual(lodashComp.versionRange, "vers:npm/>=4.17.21|<5.0.0");
|
|
1519
|
+
assert.strictEqual(lodashComp.type, "library");
|
|
1520
|
+
assert.ok(lodashComp.purl.includes("pkg:npm/lodash"));
|
|
1521
|
+
const axiosComp = result.components.find((c) => c.name === "axios");
|
|
1522
|
+
assert.ok(axiosComp);
|
|
1523
|
+
assert.strictEqual(axiosComp.versionRange, "vers:npm/1.6.0");
|
|
1524
|
+
// Should create dependency tree entry
|
|
1525
|
+
assert.strictEqual(result.dependencies.length, 1);
|
|
1526
|
+
assert.strictEqual(
|
|
1527
|
+
result.dependencies[0].ref,
|
|
1528
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1529
|
+
);
|
|
1530
|
+
assert.ok(result.dependencies[0].dependsOn.length === 2);
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
it("should parse devDependencies with optional scope", () => {
|
|
1534
|
+
const pkg = {
|
|
1535
|
+
name: "test-ext",
|
|
1536
|
+
devDependencies: {
|
|
1537
|
+
typescript: "^5.0.0",
|
|
1538
|
+
esbuild: "^0.19.0",
|
|
1539
|
+
},
|
|
1540
|
+
};
|
|
1541
|
+
const result = parseExtensionDependencies(
|
|
1542
|
+
pkg,
|
|
1543
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1544
|
+
);
|
|
1545
|
+
assert.strictEqual(result.components.length, 2);
|
|
1546
|
+
for (const comp of result.components) {
|
|
1547
|
+
assert.strictEqual(comp.scope, "optional");
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it("should parse scoped npm packages correctly", () => {
|
|
1552
|
+
const pkg = {
|
|
1553
|
+
name: "test-ext",
|
|
1554
|
+
dependencies: {
|
|
1555
|
+
"@vscode/python-extension": "^1.0.5",
|
|
1556
|
+
"@types/node": "^20.0.0",
|
|
1557
|
+
},
|
|
1558
|
+
};
|
|
1559
|
+
const result = parseExtensionDependencies(
|
|
1560
|
+
pkg,
|
|
1561
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1562
|
+
);
|
|
1563
|
+
assert.strictEqual(result.components.length, 2);
|
|
1564
|
+
const vscodePy = result.components.find(
|
|
1565
|
+
(c) => c.name === "python-extension",
|
|
1566
|
+
);
|
|
1567
|
+
assert.ok(vscodePy);
|
|
1568
|
+
assert.strictEqual(vscodePy.group, "@vscode");
|
|
1569
|
+
assert.ok(vscodePy.purl.includes("pkg:npm/%40vscode/python-extension"));
|
|
1570
|
+
const typesNode = result.components.find((c) => c.name === "node");
|
|
1571
|
+
assert.ok(typesNode);
|
|
1572
|
+
assert.strictEqual(typesNode.group, "@types");
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
it("should handle peerDependencies and optionalDependencies", () => {
|
|
1576
|
+
const pkg = {
|
|
1577
|
+
name: "test-ext",
|
|
1578
|
+
peerDependencies: {
|
|
1579
|
+
react: "^18.0.0",
|
|
1580
|
+
},
|
|
1581
|
+
optionalDependencies: {
|
|
1582
|
+
fsevents: "^2.3.0",
|
|
1583
|
+
},
|
|
1584
|
+
};
|
|
1585
|
+
const result = parseExtensionDependencies(
|
|
1586
|
+
pkg,
|
|
1587
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1588
|
+
);
|
|
1589
|
+
assert.strictEqual(result.components.length, 2);
|
|
1590
|
+
for (const comp of result.components) {
|
|
1591
|
+
assert.strictEqual(comp.scope, "optional");
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
it("should deduplicate packages across dependency groups", () => {
|
|
1596
|
+
const pkg = {
|
|
1597
|
+
name: "test-ext",
|
|
1598
|
+
dependencies: {
|
|
1599
|
+
lodash: "^4.17.21",
|
|
1600
|
+
},
|
|
1601
|
+
devDependencies: {
|
|
1602
|
+
lodash: "^4.17.21",
|
|
1603
|
+
},
|
|
1604
|
+
};
|
|
1605
|
+
const result = parseExtensionDependencies(
|
|
1606
|
+
pkg,
|
|
1607
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1608
|
+
);
|
|
1609
|
+
assert.strictEqual(
|
|
1610
|
+
result.components.length,
|
|
1611
|
+
1,
|
|
1612
|
+
"Should deduplicate lodash",
|
|
1613
|
+
);
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
it("should skip workspace:* and latest version ranges", () => {
|
|
1617
|
+
const pkg = {
|
|
1618
|
+
name: "test-ext",
|
|
1619
|
+
dependencies: {
|
|
1620
|
+
"real-dep": "^1.0.0",
|
|
1621
|
+
},
|
|
1622
|
+
devDependencies: {
|
|
1623
|
+
"workspace-dep": "workspace:*",
|
|
1624
|
+
"latest-dep": "latest",
|
|
1625
|
+
},
|
|
1626
|
+
};
|
|
1627
|
+
const result = parseExtensionDependencies(
|
|
1628
|
+
pkg,
|
|
1629
|
+
"pkg:vscode-extension/pub/test-ext@1.0.0",
|
|
1630
|
+
);
|
|
1631
|
+
// All three should be created as components
|
|
1632
|
+
assert.strictEqual(result.components.length, 3);
|
|
1633
|
+
// But workspace and latest should have no versionRange
|
|
1634
|
+
const wsDep = result.components.find((c) => c.name === "workspace-dep");
|
|
1635
|
+
assert.ok(wsDep);
|
|
1636
|
+
assert.strictEqual(wsDep.versionRange, undefined);
|
|
1637
|
+
const latestDep = result.components.find((c) => c.name === "latest-dep");
|
|
1638
|
+
assert.ok(latestDep);
|
|
1639
|
+
assert.strictEqual(latestDep.versionRange, undefined);
|
|
1640
|
+
const realDep = result.components.find((c) => c.name === "real-dep");
|
|
1641
|
+
assert.ok(realDep);
|
|
1642
|
+
assert.strictEqual(realDep.versionRange, "vers:npm/>=1.0.0|<2.0.0");
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
it("should handle real-world Pyrefly dependencies", () => {
|
|
1646
|
+
const pkg = {
|
|
1647
|
+
name: "pyrefly",
|
|
1648
|
+
publisher: "meta",
|
|
1649
|
+
version: "0.61.0",
|
|
1650
|
+
dependencies: {
|
|
1651
|
+
"@vscode/python-extension": "^1.0.5",
|
|
1652
|
+
"serialize-javascript": "^7.0.5",
|
|
1653
|
+
underscore: "^1.13.8",
|
|
1654
|
+
vsce: "^2.15.0",
|
|
1655
|
+
"vscode-languageclient": "9.0.1",
|
|
1656
|
+
},
|
|
1657
|
+
devDependencies: {
|
|
1658
|
+
"@types/mocha": "^10.0.10",
|
|
1659
|
+
"@types/node": "^16.11.7",
|
|
1660
|
+
"@types/vscode": "^1.78.1",
|
|
1661
|
+
"@vscode/test-cli": "^0.0.10",
|
|
1662
|
+
"@vscode/test-electron": "^2.5.2",
|
|
1663
|
+
"@vscode/vsce": "^2.9.2",
|
|
1664
|
+
esbuild: "^0.25.1",
|
|
1665
|
+
"npm-run-all": "^4.1.5",
|
|
1666
|
+
typescript: "^4.4.3",
|
|
1667
|
+
},
|
|
1668
|
+
};
|
|
1669
|
+
const result = parseExtensionDependencies(
|
|
1670
|
+
pkg,
|
|
1671
|
+
"pkg:vscode-extension/meta/pyrefly@0.61.0",
|
|
1672
|
+
);
|
|
1673
|
+
// 5 deps + 9 devDeps = 14 total (no overlap)
|
|
1674
|
+
assert.strictEqual(result.components.length, 14);
|
|
1675
|
+
// Verify dependency tree
|
|
1676
|
+
assert.strictEqual(result.dependencies.length, 1);
|
|
1677
|
+
assert.strictEqual(
|
|
1678
|
+
result.dependencies[0].ref,
|
|
1679
|
+
"pkg:vscode-extension/meta/pyrefly@0.61.0",
|
|
1680
|
+
);
|
|
1681
|
+
assert.strictEqual(result.dependencies[0].dependsOn.length, 14);
|
|
1682
|
+
// Check scopes
|
|
1683
|
+
const requiredComps = result.components.filter(
|
|
1684
|
+
(c) => c.scope === "required",
|
|
1685
|
+
);
|
|
1686
|
+
const optionalComps = result.components.filter(
|
|
1687
|
+
(c) => c.scope === "optional",
|
|
1688
|
+
);
|
|
1689
|
+
assert.strictEqual(requiredComps.length, 5);
|
|
1690
|
+
assert.strictEqual(optionalComps.length, 9);
|
|
1691
|
+
// Check specific component
|
|
1692
|
+
const vscLangClient = result.components.find(
|
|
1693
|
+
(c) => c.name === "vscode-languageclient",
|
|
1694
|
+
);
|
|
1695
|
+
assert.ok(vscLangClient);
|
|
1696
|
+
assert.strictEqual(vscLangClient.scope, "required");
|
|
1697
|
+
assert.strictEqual(vscLangClient.versionRange, "vers:npm/9.0.1");
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
describe("toComponent", () => {
|
|
1702
|
+
it("should return undefined for undefined input", () => {
|
|
1703
|
+
assert.strictEqual(toComponent(undefined), undefined);
|
|
1704
|
+
assert.strictEqual(toComponent(null), undefined);
|
|
1705
|
+
assert.strictEqual(toComponent({}), undefined);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
it("should create a component with publisher as namespace", () => {
|
|
1709
|
+
const extInfo = {
|
|
1710
|
+
publisher: "ms-python",
|
|
1711
|
+
name: "python",
|
|
1712
|
+
version: "2023.25.0",
|
|
1713
|
+
displayName: "Python",
|
|
1714
|
+
description: "Python language support",
|
|
1715
|
+
platform: "",
|
|
1716
|
+
};
|
|
1717
|
+
const component = toComponent(extInfo);
|
|
1718
|
+
assert.ok(component);
|
|
1719
|
+
assert.strictEqual(component.name, "python");
|
|
1720
|
+
assert.strictEqual(component.group, "ms-python");
|
|
1721
|
+
assert.strictEqual(component.version, "2023.25.0");
|
|
1722
|
+
assert.ok(
|
|
1723
|
+
component.purl.startsWith(
|
|
1724
|
+
"pkg:vscode-extension/ms-python/python@2023.25.0",
|
|
1725
|
+
),
|
|
1726
|
+
);
|
|
1727
|
+
assert.strictEqual(component.type, "application");
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
it("should include platform qualifier when present", () => {
|
|
1731
|
+
const extInfo = {
|
|
1732
|
+
publisher: "golang",
|
|
1733
|
+
name: "go",
|
|
1734
|
+
version: "0.39.1",
|
|
1735
|
+
displayName: "Go",
|
|
1736
|
+
description: "",
|
|
1737
|
+
platform: "win32-x64",
|
|
1738
|
+
};
|
|
1739
|
+
const component = toComponent(extInfo);
|
|
1740
|
+
assert.ok(component);
|
|
1741
|
+
assert.ok(component.purl.includes("platform=win32-x64"));
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it("should include IDE name in properties", () => {
|
|
1745
|
+
const extInfo = {
|
|
1746
|
+
publisher: "ms-python",
|
|
1747
|
+
name: "python",
|
|
1748
|
+
version: "1.0.0",
|
|
1749
|
+
displayName: "",
|
|
1750
|
+
description: "",
|
|
1751
|
+
platform: "",
|
|
1752
|
+
};
|
|
1753
|
+
const component = toComponent(extInfo, "Cursor");
|
|
1754
|
+
assert.ok(component);
|
|
1755
|
+
assert.ok(
|
|
1756
|
+
component.properties?.some(
|
|
1757
|
+
(p) => p.name === "cdx:vscode-extension:ide" && p.value === "Cursor",
|
|
1758
|
+
),
|
|
1759
|
+
);
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
it("should include srcPath in properties", () => {
|
|
1763
|
+
const extInfo = {
|
|
1764
|
+
publisher: "test",
|
|
1765
|
+
name: "myext",
|
|
1766
|
+
version: "1.0.0",
|
|
1767
|
+
displayName: "",
|
|
1768
|
+
description: "",
|
|
1769
|
+
platform: "",
|
|
1770
|
+
srcPath: "/some/path",
|
|
1771
|
+
};
|
|
1772
|
+
const component = toComponent(extInfo);
|
|
1773
|
+
assert.ok(component);
|
|
1774
|
+
assert.ok(
|
|
1775
|
+
component.properties?.some(
|
|
1776
|
+
(p) => p.name === "SrcFile" && p.value === "/some/path",
|
|
1777
|
+
),
|
|
1778
|
+
);
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
it("should include evidence field", () => {
|
|
1782
|
+
const extInfo = {
|
|
1783
|
+
publisher: "test",
|
|
1784
|
+
name: "myext",
|
|
1785
|
+
version: "1.0.0",
|
|
1786
|
+
displayName: "",
|
|
1787
|
+
description: "",
|
|
1788
|
+
platform: "",
|
|
1789
|
+
};
|
|
1790
|
+
const component = toComponent(extInfo);
|
|
1791
|
+
assert.ok(component);
|
|
1792
|
+
assert.ok(component.evidence);
|
|
1793
|
+
assert.ok(component.evidence.identity);
|
|
1794
|
+
assert.strictEqual(component.evidence.identity.field, "purl");
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
it("should handle extension with no publisher", () => {
|
|
1798
|
+
const extInfo = {
|
|
1799
|
+
publisher: "",
|
|
1800
|
+
name: "standalone-ext",
|
|
1801
|
+
version: "1.0.0",
|
|
1802
|
+
displayName: "",
|
|
1803
|
+
description: "",
|
|
1804
|
+
platform: "",
|
|
1805
|
+
};
|
|
1806
|
+
const component = toComponent(extInfo);
|
|
1807
|
+
assert.ok(component);
|
|
1808
|
+
assert.ok(
|
|
1809
|
+
component.purl.includes("pkg:vscode-extension/standalone-ext@1.0.0"),
|
|
1810
|
+
);
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
it("should include capability properties", () => {
|
|
1814
|
+
const extInfo = {
|
|
1815
|
+
publisher: "ms-python",
|
|
1816
|
+
name: "python",
|
|
1817
|
+
version: "1.0.0",
|
|
1818
|
+
displayName: "Python",
|
|
1819
|
+
description: "",
|
|
1820
|
+
platform: "",
|
|
1821
|
+
capabilities: {
|
|
1822
|
+
activationEvents: ["onLanguage:python", "*"],
|
|
1823
|
+
extensionKind: ["workspace"],
|
|
1824
|
+
extensionDependencies: ["ms-python.vscode-pylance"],
|
|
1825
|
+
contributes: ["commands:5", "debuggers:1", "terminal-access"],
|
|
1826
|
+
main: "./dist/extension.js",
|
|
1827
|
+
lifecycleScripts: ["postinstall", "vscode:prepublish"],
|
|
1828
|
+
untrustedWorkspaces: { supported: "limited" },
|
|
1829
|
+
virtualWorkspaces: false,
|
|
1830
|
+
},
|
|
1831
|
+
};
|
|
1832
|
+
const component = toComponent(extInfo);
|
|
1833
|
+
assert.ok(component);
|
|
1834
|
+
const props = component.properties;
|
|
1835
|
+
assert.ok(props);
|
|
1836
|
+
|
|
1837
|
+
// Check activation events
|
|
1838
|
+
const activationProp = props.find(
|
|
1839
|
+
(p) => p.name === "cdx:vscode-extension:activationEvents",
|
|
1840
|
+
);
|
|
1841
|
+
assert.ok(activationProp);
|
|
1842
|
+
assert.ok(activationProp.value.includes("onLanguage:python"));
|
|
1843
|
+
assert.ok(activationProp.value.includes("*"));
|
|
1844
|
+
|
|
1845
|
+
// Check extension kind
|
|
1846
|
+
const kindProp = props.find(
|
|
1847
|
+
(p) => p.name === "cdx:vscode-extension:extensionKind",
|
|
1848
|
+
);
|
|
1849
|
+
assert.ok(kindProp);
|
|
1850
|
+
assert.strictEqual(kindProp.value, "workspace");
|
|
1851
|
+
|
|
1852
|
+
// Check extension dependencies
|
|
1853
|
+
const depProp = props.find(
|
|
1854
|
+
(p) => p.name === "cdx:vscode-extension:extensionDependencies",
|
|
1855
|
+
);
|
|
1856
|
+
assert.ok(depProp);
|
|
1857
|
+
assert.ok(depProp.value.includes("ms-python.vscode-pylance"));
|
|
1858
|
+
|
|
1859
|
+
// Check contributes
|
|
1860
|
+
const contributesProp = props.find(
|
|
1861
|
+
(p) => p.name === "cdx:vscode-extension:contributes",
|
|
1862
|
+
);
|
|
1863
|
+
assert.ok(contributesProp);
|
|
1864
|
+
assert.ok(contributesProp.value.includes("commands:5"));
|
|
1865
|
+
assert.ok(contributesProp.value.includes("terminal-access"));
|
|
1866
|
+
|
|
1867
|
+
// Check main entry point
|
|
1868
|
+
const mainProp = props.find((p) => p.name === "cdx:vscode-extension:main");
|
|
1869
|
+
assert.ok(mainProp);
|
|
1870
|
+
assert.strictEqual(mainProp.value, "./dist/extension.js");
|
|
1871
|
+
|
|
1872
|
+
// Check lifecycle scripts
|
|
1873
|
+
const scriptsProp = props.find(
|
|
1874
|
+
(p) => p.name === "cdx:vscode-extension:lifecycleScripts",
|
|
1875
|
+
);
|
|
1876
|
+
assert.ok(scriptsProp);
|
|
1877
|
+
assert.ok(scriptsProp.value.includes("postinstall"));
|
|
1878
|
+
assert.ok(scriptsProp.value.includes("vscode:prepublish"));
|
|
1879
|
+
|
|
1880
|
+
// Check untrusted workspaces
|
|
1881
|
+
const trustProp = props.find(
|
|
1882
|
+
(p) => p.name === "cdx:vscode-extension:untrustedWorkspaces",
|
|
1883
|
+
);
|
|
1884
|
+
assert.ok(trustProp);
|
|
1885
|
+
assert.strictEqual(trustProp.value, "limited");
|
|
1886
|
+
|
|
1887
|
+
// Check virtual workspaces
|
|
1888
|
+
const vwsProp = props.find(
|
|
1889
|
+
(p) => p.name === "cdx:vscode-extension:virtualWorkspaces",
|
|
1890
|
+
);
|
|
1891
|
+
assert.ok(vwsProp);
|
|
1892
|
+
assert.strictEqual(vwsProp.value, "false");
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
it("should include manifest Properties fields", () => {
|
|
1896
|
+
const extInfo = {
|
|
1897
|
+
publisher: "meta",
|
|
1898
|
+
name: "pyrefly",
|
|
1899
|
+
version: "0.61.0",
|
|
1900
|
+
displayName: "Pyrefly",
|
|
1901
|
+
description: "Python tooling",
|
|
1902
|
+
platform: "win32-x64",
|
|
1903
|
+
executesCode: true,
|
|
1904
|
+
vscodeEngine: "^1.94.0",
|
|
1905
|
+
extensionDependencies: ["ms-python.python"],
|
|
1906
|
+
extensionKind: ["workspace"],
|
|
1907
|
+
links: {
|
|
1908
|
+
Source: "https://github.com/facebook/pyrefly.git",
|
|
1909
|
+
GitHub: "https://github.com/facebook/pyrefly.git",
|
|
1910
|
+
Support: "https://github.com/facebook/pyrefly/issues",
|
|
1911
|
+
Learn: "https://github.com/facebook/pyrefly#readme",
|
|
1912
|
+
Getstarted: "https://github.com/facebook/pyrefly.git",
|
|
1913
|
+
},
|
|
1914
|
+
};
|
|
1915
|
+
const component = toComponent(extInfo);
|
|
1916
|
+
assert.ok(component);
|
|
1917
|
+
const props = component.properties;
|
|
1918
|
+
assert.ok(props);
|
|
1919
|
+
|
|
1920
|
+
// Check executesCode
|
|
1921
|
+
const execProp = props.find(
|
|
1922
|
+
(p) => p.name === "cdx:vscode-extension:executesCode",
|
|
1923
|
+
);
|
|
1924
|
+
assert.ok(execProp);
|
|
1925
|
+
assert.strictEqual(execProp.value, "true");
|
|
1926
|
+
|
|
1927
|
+
// Check vscodeEngine
|
|
1928
|
+
const engineProp = props.find(
|
|
1929
|
+
(p) => p.name === "cdx:vscode-extension:vscodeEngine",
|
|
1930
|
+
);
|
|
1931
|
+
assert.ok(engineProp);
|
|
1932
|
+
assert.strictEqual(engineProp.value, "^1.94.0");
|
|
1933
|
+
|
|
1934
|
+
// Check extensionDependencies from manifest
|
|
1935
|
+
const depProp = props.find(
|
|
1936
|
+
(p) => p.name === "cdx:vscode-extension:extensionDependencies",
|
|
1937
|
+
);
|
|
1938
|
+
assert.ok(depProp);
|
|
1939
|
+
assert.strictEqual(depProp.value, "ms-python.python");
|
|
1940
|
+
|
|
1941
|
+
// Check extensionKind from manifest
|
|
1942
|
+
const kindProp = props.find(
|
|
1943
|
+
(p) => p.name === "cdx:vscode-extension:extensionKind",
|
|
1944
|
+
);
|
|
1945
|
+
assert.ok(kindProp);
|
|
1946
|
+
assert.strictEqual(kindProp.value, "workspace");
|
|
1947
|
+
|
|
1948
|
+
// Check externalReferences from links
|
|
1949
|
+
assert.ok(component.externalReferences);
|
|
1950
|
+
assert.ok(
|
|
1951
|
+
component.externalReferences.some(
|
|
1952
|
+
(r) =>
|
|
1953
|
+
r.type === "vcs" &&
|
|
1954
|
+
r.url === "https://github.com/facebook/pyrefly.git",
|
|
1955
|
+
),
|
|
1956
|
+
);
|
|
1957
|
+
assert.ok(
|
|
1958
|
+
component.externalReferences.some(
|
|
1959
|
+
(r) =>
|
|
1960
|
+
r.type === "issue-tracker" &&
|
|
1961
|
+
r.url === "https://github.com/facebook/pyrefly/issues",
|
|
1962
|
+
),
|
|
1963
|
+
);
|
|
1964
|
+
assert.ok(
|
|
1965
|
+
component.externalReferences.some(
|
|
1966
|
+
(r) =>
|
|
1967
|
+
r.type === "documentation" &&
|
|
1968
|
+
r.url === "https://github.com/facebook/pyrefly#readme",
|
|
1969
|
+
),
|
|
1970
|
+
);
|
|
1971
|
+
assert.ok(
|
|
1972
|
+
component.externalReferences.some(
|
|
1973
|
+
(r) =>
|
|
1974
|
+
r.type === "website" &&
|
|
1975
|
+
r.url === "https://github.com/facebook/pyrefly.git",
|
|
1976
|
+
),
|
|
1977
|
+
);
|
|
1978
|
+
});
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
describe("parseExtensionDirName", () => {
|
|
1982
|
+
it("should parse publisher.name-version pattern", () => {
|
|
1983
|
+
const component = parseExtensionDirName(
|
|
1984
|
+
"/home/user/.vscode/extensions/ms-python.python-2023.25.0",
|
|
1985
|
+
);
|
|
1986
|
+
assert.ok(component);
|
|
1987
|
+
assert.strictEqual(component.group, "ms-python");
|
|
1988
|
+
assert.strictEqual(component.name, "python");
|
|
1989
|
+
assert.strictEqual(component.version, "2023.25.0");
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
it("should parse complex extension names", () => {
|
|
1993
|
+
const component = parseExtensionDirName(
|
|
1994
|
+
"/home/user/.vscode/extensions/redhat.vscode-xml-0.27.1",
|
|
1995
|
+
);
|
|
1996
|
+
assert.ok(component);
|
|
1997
|
+
assert.strictEqual(component.group, "redhat");
|
|
1998
|
+
assert.strictEqual(component.name, "vscode-xml");
|
|
1999
|
+
assert.strictEqual(component.version, "0.27.1");
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
it("should return undefined for non-matching names", () => {
|
|
2003
|
+
assert.strictEqual(parseExtensionDirName("/some/random/path"), undefined);
|
|
2004
|
+
assert.strictEqual(parseExtensionDirName(""), undefined);
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
it("should handle Windows paths", () => {
|
|
2008
|
+
const component = parseExtensionDirName(
|
|
2009
|
+
"C:\\Users\\test\\.vscode\\extensions\\golang.go-0.39.1",
|
|
2010
|
+
);
|
|
2011
|
+
assert.ok(component);
|
|
2012
|
+
assert.strictEqual(component.group, "golang");
|
|
2013
|
+
assert.strictEqual(component.name, "go");
|
|
2014
|
+
assert.strictEqual(component.version, "0.39.1");
|
|
2015
|
+
});
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
describe("parseInstalledExtensionDir", () => {
|
|
2019
|
+
const testDir = join(baseTempDir, "test-installed");
|
|
2020
|
+
|
|
2021
|
+
it("should parse extension dir with package.json", () => {
|
|
2022
|
+
const extDir = join(testDir, "ms-python.python-2023.25.0");
|
|
2023
|
+
mkdirSync(extDir, { recursive: true });
|
|
2024
|
+
writeFileSync(
|
|
2025
|
+
join(extDir, "package.json"),
|
|
2026
|
+
JSON.stringify({
|
|
2027
|
+
name: "python",
|
|
2028
|
+
publisher: "ms-python",
|
|
2029
|
+
version: "2023.25.0",
|
|
2030
|
+
displayName: "Python",
|
|
2031
|
+
description: "Python language support",
|
|
2032
|
+
}),
|
|
2033
|
+
);
|
|
2034
|
+
const component = parseInstalledExtensionDir(extDir, "VS Code");
|
|
2035
|
+
assert.ok(component);
|
|
2036
|
+
assert.strictEqual(component.name, "python");
|
|
2037
|
+
assert.strictEqual(component.group, "ms-python");
|
|
2038
|
+
assert.strictEqual(component.version, "2023.25.0");
|
|
2039
|
+
assert.ok(
|
|
2040
|
+
component.purl.startsWith(
|
|
2041
|
+
"pkg:vscode-extension/ms-python/python@2023.25.0",
|
|
2042
|
+
),
|
|
2043
|
+
);
|
|
2044
|
+
assert.ok(
|
|
2045
|
+
component.properties?.some(
|
|
2046
|
+
(p) => p.name === "cdx:vscode-extension:ide" && p.value === "VS Code",
|
|
2047
|
+
),
|
|
2048
|
+
);
|
|
2049
|
+
});
|
|
2050
|
+
|
|
2051
|
+
it("should parse extension dir with package.json and extract capabilities", () => {
|
|
2052
|
+
const extDir = join(testDir, "ms-python.python-cap-2023.25.0");
|
|
2053
|
+
mkdirSync(extDir, { recursive: true });
|
|
2054
|
+
writeFileSync(
|
|
2055
|
+
join(extDir, "package.json"),
|
|
2056
|
+
JSON.stringify({
|
|
2057
|
+
name: "python",
|
|
2058
|
+
publisher: "ms-python",
|
|
2059
|
+
version: "2023.25.0",
|
|
2060
|
+
displayName: "Python",
|
|
2061
|
+
main: "./dist/extension.js",
|
|
2062
|
+
activationEvents: ["onLanguage:python"],
|
|
2063
|
+
contributes: {
|
|
2064
|
+
commands: [{ command: "python.run", title: "Run" }],
|
|
2065
|
+
debuggers: [{ type: "python", label: "Python" }],
|
|
2066
|
+
},
|
|
2067
|
+
extensionDependencies: ["ms-python.vscode-pylance"],
|
|
2068
|
+
}),
|
|
2069
|
+
);
|
|
2070
|
+
const component = parseInstalledExtensionDir(extDir, "VS Code");
|
|
2071
|
+
assert.ok(component);
|
|
2072
|
+
// Should have capability properties
|
|
2073
|
+
assert.ok(
|
|
2074
|
+
component.properties?.some(
|
|
2075
|
+
(p) => p.name === "cdx:vscode-extension:activationEvents",
|
|
2076
|
+
),
|
|
2077
|
+
"Should extract activationEvents",
|
|
2078
|
+
);
|
|
2079
|
+
assert.ok(
|
|
2080
|
+
component.properties?.some((p) => p.name === "cdx:vscode-extension:main"),
|
|
2081
|
+
"Should extract main entry point",
|
|
2082
|
+
);
|
|
2083
|
+
assert.ok(
|
|
2084
|
+
component.properties?.some(
|
|
2085
|
+
(p) => p.name === "cdx:vscode-extension:contributes",
|
|
2086
|
+
),
|
|
2087
|
+
"Should extract contributed features",
|
|
2088
|
+
);
|
|
2089
|
+
assert.ok(
|
|
2090
|
+
component.properties?.some(
|
|
2091
|
+
(p) => p.name === "cdx:vscode-extension:extensionDependencies",
|
|
2092
|
+
),
|
|
2093
|
+
"Should extract extension dependencies",
|
|
2094
|
+
);
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
it("should parse extension dir with .vsixmanifest", () => {
|
|
2098
|
+
const extDir = join(testDir, "golang.go-0.39.1");
|
|
2099
|
+
mkdirSync(extDir, { recursive: true });
|
|
2100
|
+
writeFileSync(
|
|
2101
|
+
join(extDir, ".vsixmanifest"),
|
|
2102
|
+
`<?xml version="1.0" encoding="utf-8"?>
|
|
2103
|
+
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
|
|
2104
|
+
<Metadata>
|
|
2105
|
+
<Identity Id="go" Version="0.39.1" Publisher="golang" />
|
|
2106
|
+
<DisplayName>Go</DisplayName>
|
|
2107
|
+
<Description>Go language support</Description>
|
|
2108
|
+
</Metadata>
|
|
2109
|
+
</PackageManifest>`,
|
|
2110
|
+
);
|
|
2111
|
+
const component = parseInstalledExtensionDir(extDir, "VS Code");
|
|
2112
|
+
assert.ok(component);
|
|
2113
|
+
assert.strictEqual(component.name, "go");
|
|
2114
|
+
assert.strictEqual(component.group, "golang");
|
|
2115
|
+
assert.strictEqual(component.version, "0.39.1");
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
it("should fall back to directory name parsing", () => {
|
|
2119
|
+
const extDir = join(testDir, "redhat.vscode-yaml-1.14.0");
|
|
2120
|
+
mkdirSync(extDir, { recursive: true });
|
|
2121
|
+
// No package.json or .vsixmanifest
|
|
2122
|
+
const component = parseInstalledExtensionDir(extDir);
|
|
2123
|
+
assert.ok(component);
|
|
2124
|
+
assert.strictEqual(component.group, "redhat");
|
|
2125
|
+
assert.strictEqual(component.name, "vscode-yaml");
|
|
2126
|
+
assert.strictEqual(component.version, "1.14.0");
|
|
2127
|
+
});
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
describe("collectInstalledExtensions", () => {
|
|
2131
|
+
const testDir = join(baseTempDir, "test-collect");
|
|
2132
|
+
const extDir = join(testDir, "extensions");
|
|
2133
|
+
|
|
2134
|
+
it("should collect extensions from an extensions directory", () => {
|
|
2135
|
+
// Create mock extension dirs
|
|
2136
|
+
const ext1 = join(extDir, "ms-python.python-2023.25.0");
|
|
2137
|
+
const ext2 = join(extDir, "golang.go-0.39.1");
|
|
2138
|
+
mkdirSync(ext1, { recursive: true });
|
|
2139
|
+
mkdirSync(ext2, { recursive: true });
|
|
2140
|
+
writeFileSync(
|
|
2141
|
+
join(ext1, "package.json"),
|
|
2142
|
+
JSON.stringify({
|
|
2143
|
+
name: "python",
|
|
2144
|
+
publisher: "ms-python",
|
|
2145
|
+
version: "2023.25.0",
|
|
2146
|
+
}),
|
|
2147
|
+
);
|
|
2148
|
+
writeFileSync(
|
|
2149
|
+
join(ext2, "package.json"),
|
|
2150
|
+
JSON.stringify({
|
|
2151
|
+
name: "go",
|
|
2152
|
+
publisher: "golang",
|
|
2153
|
+
version: "0.39.1",
|
|
2154
|
+
}),
|
|
2155
|
+
);
|
|
2156
|
+
|
|
2157
|
+
const components = collectInstalledExtensions([
|
|
2158
|
+
{ name: "VS Code", dir: extDir },
|
|
2159
|
+
]);
|
|
2160
|
+
assert.ok(Array.isArray(components));
|
|
2161
|
+
assert.strictEqual(components.length, 2);
|
|
2162
|
+
const names = components.map((c) => c.name);
|
|
2163
|
+
assert.ok(names.includes("python"));
|
|
2164
|
+
assert.ok(names.includes("go"));
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
it("should skip hidden directories", () => {
|
|
2168
|
+
const hiddenDir = join(extDir, ".obsolete");
|
|
2169
|
+
mkdirSync(hiddenDir, { recursive: true });
|
|
2170
|
+
writeFileSync(
|
|
2171
|
+
join(hiddenDir, "package.json"),
|
|
2172
|
+
JSON.stringify({
|
|
2173
|
+
name: "old-ext",
|
|
2174
|
+
publisher: "test",
|
|
2175
|
+
version: "1.0.0",
|
|
2176
|
+
}),
|
|
2177
|
+
);
|
|
2178
|
+
|
|
2179
|
+
const components = collectInstalledExtensions([
|
|
2180
|
+
{ name: "VS Code", dir: extDir },
|
|
2181
|
+
]);
|
|
2182
|
+
const names = components.map((c) => c.name);
|
|
2183
|
+
assert.ok(!names.includes("old-ext"), "Should not include hidden dirs");
|
|
2184
|
+
});
|
|
2185
|
+
|
|
2186
|
+
it("should deduplicate by purl", () => {
|
|
2187
|
+
// Same extension in two different IDE dirs
|
|
2188
|
+
const ideDir1 = join(testDir, "ide1-ext");
|
|
2189
|
+
const ideDir2 = join(testDir, "ide2-ext");
|
|
2190
|
+
const ext1 = join(ideDir1, "ms-python.python-2023.25.0");
|
|
2191
|
+
const ext2 = join(ideDir2, "ms-python.python-2023.25.0");
|
|
2192
|
+
mkdirSync(ext1, { recursive: true });
|
|
2193
|
+
mkdirSync(ext2, { recursive: true });
|
|
2194
|
+
const pkgJson = JSON.stringify({
|
|
2195
|
+
name: "python",
|
|
2196
|
+
publisher: "ms-python",
|
|
2197
|
+
version: "2023.25.0",
|
|
2198
|
+
});
|
|
2199
|
+
writeFileSync(join(ext1, "package.json"), pkgJson);
|
|
2200
|
+
writeFileSync(join(ext2, "package.json"), pkgJson);
|
|
2201
|
+
|
|
2202
|
+
const components = collectInstalledExtensions([
|
|
2203
|
+
{ name: "IDE1", dir: ideDir1 },
|
|
2204
|
+
{ name: "IDE2", dir: ideDir2 },
|
|
2205
|
+
]);
|
|
2206
|
+
const pythonComponents = components.filter((c) => c.name === "python");
|
|
2207
|
+
assert.strictEqual(
|
|
2208
|
+
pythonComponents.length,
|
|
2209
|
+
1,
|
|
2210
|
+
"Should deduplicate by purl",
|
|
2211
|
+
);
|
|
2212
|
+
});
|
|
2213
|
+
|
|
2214
|
+
it("should handle non-existent directory gracefully", () => {
|
|
2215
|
+
const components = collectInstalledExtensions([
|
|
2216
|
+
{ name: "Nonexistent", dir: "/nonexistent/path/that/does/not/exist" },
|
|
2217
|
+
]);
|
|
2218
|
+
assert.ok(Array.isArray(components));
|
|
2219
|
+
assert.strictEqual(components.length, 0);
|
|
2220
|
+
});
|
|
2221
|
+
});
|
|
2222
|
+
|
|
2223
|
+
describe("cleanupTempDir", () => {
|
|
2224
|
+
it("should not throw for null/undefined", () => {
|
|
2225
|
+
assert.doesNotThrow(() => cleanupTempDir(null));
|
|
2226
|
+
assert.doesNotThrow(() => cleanupTempDir(undefined));
|
|
2227
|
+
assert.doesNotThrow(() => cleanupTempDir(""));
|
|
2228
|
+
});
|
|
2229
|
+
|
|
2230
|
+
it("should clean up a temp dir with vsix-deps- prefix", () => {
|
|
2231
|
+
const tempDir = join(tmpdir(), "vsix-deps-test-cleanup");
|
|
2232
|
+
mkdirSync(tempDir, { recursive: true });
|
|
2233
|
+
assert.ok(existsSync(tempDir));
|
|
2234
|
+
cleanupTempDir(tempDir);
|
|
2235
|
+
assert.ok(!existsSync(tempDir), "Should have been removed");
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
it("should not remove dirs without vsix-deps- prefix", () => {
|
|
2239
|
+
const tempDir = join(tmpdir(), "some-other-dir-test");
|
|
2240
|
+
mkdirSync(tempDir, { recursive: true });
|
|
2241
|
+
assert.ok(existsSync(tempDir));
|
|
2242
|
+
cleanupTempDir(tempDir);
|
|
2243
|
+
assert.ok(existsSync(tempDir), "Should NOT have been removed");
|
|
2244
|
+
// Manual cleanup
|
|
2245
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2246
|
+
});
|
|
2247
|
+
});
|