Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as cheerio from 'cheerio';
import * as url from 'url';
import { lookup } from 'mime';
import * as urljoin from 'url-join';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility } from './validation';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from './validation';
import { getDependencies } from './npm';

interface IReadFile {
Expand Down Expand Up @@ -605,6 +605,10 @@ export function validateManifest(manifest: Manifest): Manifest {

validateEngineCompatibility(manifest.engines['vscode']);

if (manifest.devDependencies && manifest.devDependencies['@types/vscode']) {
validateVSCodeTypesCompatibility(manifest.engines['vscode'], manifest.devDependencies['@types/vscode']);
}

if (/\.svg$/i.test(manifest.icon || '')) {
throw new Error(`SVGs can't be used as icons: ${manifest.icon}`);
}
Expand Down
29 changes: 28 additions & 1 deletion src/test/validation.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as assert from 'assert';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility } from '../validation';
import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from '../validation';

describe('validatePublisher', () => {
it('should throw with empty', () => {
Expand Down Expand Up @@ -98,4 +98,31 @@ describe('validateEngineCompatibility', () => {
assert.throws(() => validateVersion('>=1'));
assert.throws(() => validateVersion('>=1.0'));
});
});

describe('validateVSCodeTypesCompatibility', () => {

it('should validate', () => {
validateVSCodeTypesCompatibility('*', '1.30.0');
validateVSCodeTypesCompatibility('*', '^1.30.0');
validateVSCodeTypesCompatibility('*', '~1.30.0');

validateVSCodeTypesCompatibility('1.30.0', '1.30.0');
validateVSCodeTypesCompatibility('1.30.0', '1.20.0');

assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '^1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '~1.40.0'));

assert.throws(() => validateVSCodeTypesCompatibility('1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('^1.30.0', '1.40.0'));
assert.throws(() => validateVSCodeTypesCompatibility('~1.30.0', '1.40.0'));

assert.throws(() => validateVSCodeTypesCompatibility('1.x.x', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.x.0', '1.30.0'));

assert.throws(() => validateVSCodeTypesCompatibility('1.5.0', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30.0'));
assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30'));
});
});
60 changes: 60 additions & 0 deletions src/validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as semver from 'semver';
import * as parseSemver from 'parse-semver';

const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i;

Expand Down Expand Up @@ -40,4 +41,63 @@ export function validateEngineCompatibility(version: string): void {
if (!/^\*$|^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/.test(version)) {
throw new Error(`Invalid vscode engine compatibility version '${version}'`);
}
}

/**
* User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode
*/
export function validateVSCodeTypesCompatibility(engineVersion: string, typeVersion: string): void {
if (engineVersion === '*') {
return;
}

if (!typeVersion) {
throw new Error(`Missing @types/vscode version`);
}

let plainEngineVersion: string, plainTypeVersion: string;

try {
const engineSemver = parseSemver(`vscode@${engineVersion}`);
plainEngineVersion = engineSemver.version;
} catch (err) {
throw new Error('Failed to parse semver of engines.vscode');
}

try {
const typeSemver = parseSemver(`@types/vscode@${typeVersion}`);
plainTypeVersion = typeSemver.version;
} catch (err) {
throw new Error('Failed to parse semver of @types/vscode');
}

// For all `x`, use smallest version for comparison
plainEngineVersion = plainEngineVersion.replace(/x/g, '0');

const [typeMajor, typeMinor, typePatch] = plainTypeVersion.split('.').map(x => {
try {
return parseInt(x);
} catch (err) {
return 0;
}
});
const [engineMajor, engineMinor, enginePatch] = plainEngineVersion.split('.').map(x => {
try {
return parseInt(x);
} catch (err) {
return 0;
}
});

const error = new Error(`@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Consider upgrade engines.vscode or use an older @types/vscode version`);

if (typeof typeMajor === 'number' && typeof engineMajor === 'number' && typeMajor > engineMajor) {
throw error;
}
if (typeof typeMinor === 'number' && typeof engineMinor === 'number' && typeMinor > engineMinor) {
throw error;
}
if (typeof typePatch === 'number' && typeof enginePatch === 'number' && typePatch > enginePatch) {
throw error;
}
}