src/vs/platform/extensions/common/extensionValidator.ts

391 LOC · 310 covered · 81 uncovered · 83 ranges · 97 concepts · 20 introducers · 65 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extensionValidator.ts ×9
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { isEqualOrParent, joinPath } from '../../../base/common/resources.js';
7 > import Severity from '../../../base/common/severity.js';
8 > import { URI } from '../../../base/common/uri.js';
9 > import * as nls from '../../../nls.js';
10 > import * as semver from '../../../base/common/semver/semver.js';
11 > import { IExtensionManifest } from './extensions.js';
12 >
13 > export interface IParsedVersion {
14 > hasCaret: boolean;
15 > hasGreaterEquals: boolean;
16 > majorBase: number;
17 > majorMustEqual: boolean;
18 > minorBase: number;
19 > minorMustEqual: boolean;
20 > patchBase: number;
21 > patchMustEqual: boolean;
22 > preRelease: string | null;
23 > }
24 >
25 > export interface INormalizedVersion {
26 > majorBase: number;
27 > majorMustEqual: boolean;
28 > minorBase: number;
29 > minorMustEqual: boolean;
30 > patchBase: number;
31 > patchMustEqual: boolean;
32 > notBefore: number; /* milliseconds timestamp, or 0 */
33 > isMinimum: boolean;
34 > }
35 >
36 > const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/;
37 > const NOT_BEFORE_REGEXP = /^-(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?$/;
38 >
39 > export function isValidVersionStr(version: string): boolean {
40 > version = version.trim(); extensionValidator.ts ×1
41 > return (version === '*' || VERSION_REGEXP.test(version));
42 > }
44 > export function parseVersion(version: string): IParsedVersion | null {
45 > if (!isValidVersionStr(version)) { extensionValidator.ts ×4
46 return null;
47 }
49 > version = version.trim();
50 >
51 > if (version === '*') {
53 > hasCaret: false,
54 > hasGreaterEquals: false,
55 > majorBase: 0,
56 > majorMustEqual: false,
57 > minorBase: 0,
58 > minorMustEqual: false,
59 > patchBase: 0,
60 > patchMustEqual: false,
61 > preRelease: null
62 > };
63 > }
65 > const m = version.match(VERSION_REGEXP);
66 > if (!m) {
67 return null;
68 }
70 > hasCaret: m[1] === '^',
71 > hasGreaterEquals: m[1] === '>=',
72 > majorBase: m[2] === 'x' ? 0 : parseInt(m[2], 10),
73 > majorMustEqual: (m[2] === 'x' ? false : true),
74 > minorBase: m[4] === 'x' ? 0 : parseInt(m[4], 10),
75 > minorMustEqual: (m[4] === 'x' ? false : true),
76 > patchBase: m[6] === 'x' ? 0 : parseInt(m[6], 10),
77 > patchMustEqual: (m[6] === 'x' ? false : true),
78 > preRelease: m[8] || null
79 > };
80 > }
82 > export function normalizeVersion(version: IParsedVersion | null): INormalizedVersion | null {
83 > if (!version) { extensionValidator.ts ×4
84 return null;
85 }
87 > const majorBase = version.majorBase;
88 > const majorMustEqual = version.majorMustEqual;
89 > const minorBase = version.minorBase;
90 > let minorMustEqual = version.minorMustEqual;
91 > const patchBase = version.patchBase;
92 > let patchMustEqual = version.patchMustEqual;
93 >
94 > if (version.hasCaret) {
95 > if (majorBase === 0) {
96 > patchMustEqual = false; extensionValidator.ts ×3
98 > minorMustEqual = false;
99 > patchMustEqual = false;
100 > }
101 > }
102 >
103 > let notBefore = 0;
104 > if (version.preRelease) {
105 > const match = NOT_BEFORE_REGEXP.exec(version.preRelease); extensionValidator.ts ×3
106 > if (match) {
107 > const [, year, month, day, hours, minutes] = match; extensionValidator.ts ×1
108 > notBefore = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hours) || 0, Number(minutes) || 0);
109 > }
112 > return {
113 > majorBase: majorBase,
114 > majorMustEqual: majorMustEqual,
115 > minorBase: minorBase,
116 > minorMustEqual: minorMustEqual,
117 > patchBase: patchBase,
118 > patchMustEqual: patchMustEqual,
119 > isMinimum: version.hasGreaterEquals,
120 > notBefore,
121 > };
122 > }
124 > export function isValidVersion(_inputVersion: string | INormalizedVersion, _inputDate: ProductDate, _desiredVersion: string | INormalizedVersion): boolean {
125 > let version: INormalizedVersion | null; extensionValidator.ts ×12
126 > if (typeof _inputVersion === 'string') {
127 > version = normalizeVersion(parseVersion(_inputVersion));
128 > } else {
129 version = _inputVersion;
130 }
132 > let productTs: number | undefined;
133 > if (_inputDate instanceof Date) {
134 productTs = _inputDate.getTime();
135 > } else if (typeof _inputDate === 'string') { extensionValidator.ts ×12
136 > productTs = new Date(_inputDate).getTime(); extensionValidator.ts ×7
137 > }
139 > let desiredVersion: INormalizedVersion | null;
140 > if (typeof _desiredVersion === 'string') {
141 > desiredVersion = normalizeVersion(parseVersion(_desiredVersion)); extensionValidator.ts ×3
143 > desiredVersion = _desiredVersion; extensionValidator.ts ×6
144 > }
146 > if (!version || !desiredVersion) {
147 return false;
148 }
150 > const majorBase = version.majorBase;
151 > const minorBase = version.minorBase;
152 > const patchBase = version.patchBase;
153 >
154 > let desiredMajorBase = desiredVersion.majorBase;
155 > let desiredMinorBase = desiredVersion.minorBase;
156 > let desiredPatchBase = desiredVersion.patchBase;
157 > const desiredNotBefore = desiredVersion.notBefore;
158 >
159 > let majorMustEqual = desiredVersion.majorMustEqual;
160 > let minorMustEqual = desiredVersion.minorMustEqual;
161 > let patchMustEqual = desiredVersion.patchMustEqual;
162 >
163 > if (desiredVersion.isMinimum) {
164 > if (majorBase > desiredMajorBase) { extensionValidator.ts ×3
165 > return true;
166 > }
167 >
168 > if (majorBase < desiredMajorBase) {
169 > return false;
170 > }
171 >
172 > if (minorBase > desiredMinorBase) {
173 > return true;
174 > }
175 >
176 > if (minorBase < desiredMinorBase) {
177 > return false;
178 > }
179 >
180 > if (productTs && productTs < desiredNotBefore) {
181 return false;
182 }
184 > return patchBase >= desiredPatchBase;
185 > }
187 > // Anything < 1.0.0 is compatible with >= 1.0.0, except exact matches
188 > if (majorBase === 1 && desiredMajorBase === 0 && (!majorMustEqual || !minorMustEqual || !patchMustEqual)) {
189 > desiredMajorBase = 1; extensionValidator.ts ×7
190 > desiredMinorBase = 0;
191 > desiredPatchBase = 0;
192 > majorMustEqual = true;
193 > minorMustEqual = false;
194 > patchMustEqual = false;
195 > }
197 > if (majorBase < desiredMajorBase) {
198 > // smaller major version extensionValidator.ts ×7
199 > return false;
200 > }
202 > if (majorBase > desiredMajorBase) {
203 > // higher major version extensionValidator.ts ×7
204 > return (!majorMustEqual);
205 > }
207 > // at this point, majorBase are equal
208 >
209 > if (minorBase < desiredMinorBase) {
210 > // smaller minor version extensionValidator.ts ×1
211 > return false;
212 > }
214 > if (minorBase > desiredMinorBase) {
215 > // higher minor version extensionValidator.ts ×7
216 > return (!minorMustEqual);
217 > }
219 > // at this point, minorBase are equal
220 >
221 > if (patchBase < desiredPatchBase) {
222 > // smaller patch version extensionValidator.ts ×7
223 > return false;
224 > }
226 > if (patchBase > desiredPatchBase) {
227 > // higher patch version extensionValidator.ts ×7
228 > return (!patchMustEqual);
229 > }
231 > // at this point, patchBase are equal
232 >
233 > if (productTs && productTs < desiredNotBefore) { extensionValidator.ts ×12
234 > return false; extensionValidator.ts ×2
235 > }
237 > return true;
238 > }
240 > type ProductDate = string | Date | undefined;
241 >
242 > export function validateExtensionManifest(productVersion: string, productDate: ProductDate, extensionLocation: URI, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean): readonly [Severity, string][] {
243 > const validations: [Severity, string][] = []; extensionsScannerService.ts ×30
244 > if (typeof extensionManifest.publisher !== 'undefined' && typeof extensionManifest.publisher !== 'string') {
245 validations.push([Severity.Error, nls.localize('extensionDescription.publisher', "property publisher must be of type `string`.")]);
246 return validations;
247 }
248 > if (typeof extensionManifest.name !== 'string') { extensionsScannerService.ts ×30
249 validations.push([Severity.Error, nls.localize('extensionDescription.name', "property `{0}` is mandatory and must be of type `string`", 'name')]);
250 return validations;
251 }
252 > if (typeof extensionManifest.version !== 'string') { extensionsScannerService.ts ×30
253 validations.push([Severity.Error, nls.localize('extensionDescription.version', "property `{0}` is mandatory and must be of type `string`", 'version')]);
254 return validations;
255 }
256 > if (!extensionManifest.engines) { extensionsScannerService.ts ×30
257 validations.push([Severity.Error, nls.localize('extensionDescription.engines', "property `{0}` is mandatory and must be of type `object`", 'engines')]);
258 return validations;
259 }
260 > if (typeof extensionManifest.engines.vscode !== 'string') { extensionsScannerService.ts ×30
261 validations.push([Severity.Error, nls.localize('extensionDescription.engines.vscode', "property `{0}` is mandatory and must be of type `string`", 'engines.vscode')]);
262 return validations;
263 }
264 > if (typeof extensionManifest.extensionDependencies !== 'undefined') { extensionsScannerService.ts ×30
265 if (!isStringArray(extensionManifest.extensionDependencies)) {
266 validations.push([Severity.Error, nls.localize('extensionDescription.extensionDependencies', "property `{0}` can be omitted or must be of type `string[]`", 'extensionDependencies')]);
267 return validations;
268 }
269 }
270 > if (typeof extensionManifest.extensionAffinity !== 'undefined') { extensionsScannerService.ts ×30
271 if (!isStringArray(extensionManifest.extensionAffinity)) {
272 validations.push([Severity.Error, nls.localize('extensionDescription.extensionAffinity', "property `{0}` can be omitted or must be of type `string[]`", 'extensionAffinity')]);
273 return validations;
274 }
275 }
276 > if (typeof extensionManifest.activationEvents !== 'undefined') { extensionsScannerService.ts ×30
277 > if (!isStringArray(extensionManifest.activationEvents)) {
278 validations.push([Severity.Error, nls.localize('extensionDescription.activationEvents1', "property `{0}` can be omitted or must be of type `string[]`", 'activationEvents')]);
279 return validations;
280 }
281 > if (typeof extensionManifest.main === 'undefined' && typeof extensionManifest.browser === 'undefined') { extensionsScannerService.ts ×30
282 validations.push([Severity.Error, nls.localize('extensionDescription.activationEvents2', "property `{0}` should be omitted if the extension doesn't have a `{1}` or `{2}` property.", 'activationEvents', 'main', 'browser')]);
283 return validations;
284 }
286 > if (typeof extensionManifest.extensionKind !== 'undefined') {
287 if (typeof extensionManifest.main === 'undefined') {
288 validations.push([Severity.Warning, nls.localize('extensionDescription.extensionKind', "property `{0}` can be defined only if property `main` is also defined.", 'extensionKind')]);
289 // not a failure case
290 }
291 }
292 > if (typeof extensionManifest.main !== 'undefined') { extensionsScannerService.ts ×30
293 > if (typeof extensionManifest.main !== 'string') {
294 validations.push([Severity.Error, nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main')]);
295 return validations;
297 > const mainLocation = joinPath(extensionLocation, extensionManifest.main);
298 > if (!isEqualOrParent(mainLocation, extensionLocation)) {
299 validations.push([Severity.Warning, nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", mainLocation.path, extensionLocation.path)]);
300 // not a failure case
301 }
303 > }
304 > if (typeof extensionManifest.browser !== 'undefined') {
305 if (typeof extensionManifest.browser !== 'string') {
306 validations.push([Severity.Error, nls.localize('extensionDescription.browser1', "property `{0}` can be omitted or must be of type `string`", 'browser')]);
307 return validations;
308 } else {
309 const browserLocation = joinPath(extensionLocation, extensionManifest.browser);
310 if (!isEqualOrParent(browserLocation, extensionLocation)) {
311 validations.push([Severity.Warning, nls.localize('extensionDescription.browser2', "Expected `browser` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", browserLocation.path, extensionLocation.path)]);
312 // not a failure case
313 }
314 }
315 }
317 > if (!semver.valid(extensionManifest.version)) {
318 validations.push([Severity.Error, nls.localize('notSemver', "Extension version is not semver compatible.")]);
319 return validations;
320 }
322 > const notices: string[] = [];
323 > const validExtensionVersion = isValidExtensionVersion(productVersion, productDate, extensionManifest, extensionIsBuiltin, notices);
324 > if (!validExtensionVersion) {
325 > for (const notice of notices) { extensionsScannerService.ts ×2
326 > validations.push([Severity.Error, notice]);
327 > }
328 > }
330 > return validations;
331 > }
333 > export function isValidExtensionVersion(productVersion: string, productDate: ProductDate, extensionManifest: IExtensionManifest, extensionIsBuiltin: boolean, notices: string[]): boolean {
335 > if (extensionIsBuiltin || (typeof extensionManifest.main === 'undefined' && typeof extensionManifest.browser === 'undefined')) {
336 > // No version check for builtin or declarative extensions extensionValidator.ts ×1
337 > return true;
338 > }
340 > return isVersionValid(productVersion, productDate, extensionManifest.engines.vscode, notices);
341 > }
343 > export function isEngineValid(engine: string, version: string, date: ProductDate): boolean {
344 // TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
345 return engine === '*' || isVersionValid(version, date, engine);
346 }
348 > function isVersionValid(currentVersion: string, date: ProductDate, requestedVersion: string, notices: string[] = []): boolean { extensionValidator.ts ×6
349 >
350 > const desiredVersion = normalizeVersion(parseVersion(requestedVersion));
351 > if (!desiredVersion) {
352 notices.push(nls.localize('versionSyntax', "Could not parse `engines.vscode` value {0}. Please use, for example: ^1.22.0, ^1.22.x, etc.", requestedVersion));
353 return false;
354 }
356 > // enforce that a breaking API version is specified.
357 > // for 0.X.Y, that means up to 0.X must be specified
358 > // otherwise for Z.X.Y, that means Z must be specified
359 > if (desiredVersion.majorBase === 0) {
360 > // force that major and minor must be specific extensionValidator.ts ×2
361 > if (!desiredVersion.majorMustEqual || !desiredVersion.minorMustEqual) {
362 > notices.push(nls.localize('versionSpecificity1', "Version specified in `engines.vscode` ({0}) is not specific enough. For vscode versions before 1.0.0, please define at a minimum the major and minor desired version. E.g. ^0.10.0, 0.10.x, 0.11.0, etc.", requestedVersion));
363 > return false;
364 > }
365 > } else { extensionValidator.ts ×6
366 > // force that major must be specific
367 > if (!desiredVersion.majorMustEqual) {
368 notices.push(nls.localize('versionSpecificity2', "Version specified in `engines.vscode` ({0}) is not specific enough. For vscode versions after 1.0.0, please define at a minimum the major desired version. E.g. ^1.10.0, 1.10.x, 1.x.x, 2.x.x, etc.", requestedVersion));
369 return false;
370 }
372 >
373 > if (!isValidVersion(currentVersion, date, desiredVersion)) {
374 > notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion)); extensionValidator.ts ×1
375 > return false;
376 > }
378 > return true;
379 > }
381 > function isStringArray(arr: readonly string[]): boolean { extensionsScannerService.ts ×30
382 > if (!Array.isArray(arr)) {
383 return false;
384 }
385 > for (let i = 0, len = arr.length; i < len; i++) { extensionsScannerService.ts ×30
386 > if (typeof arr[i] !== 'string') {
387 return false;
388 }
390 > return true;
391 > }