mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
Lint redundant activation events if extension is targeting ^1.75
(#173345)
This commit is contained in:
parent
19014d03d6
commit
3ab0fb3aa9
117
extensions/extension-editing/src/extensionEngineValidation.ts
Normal file
117
extensions/extension-editing/src/extensionEngineValidation.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// https://github.com/microsoft/vscode/blob/6cb34eb22385bc93ab25aa2e5113f59c7a2299ac/src/vs/platform/extensions/common/extensionValidator.ts
|
||||
|
||||
export interface IParsedVersion {
|
||||
hasCaret: boolean;
|
||||
hasGreaterEquals: boolean;
|
||||
majorBase: number;
|
||||
majorMustEqual: boolean;
|
||||
minorBase: number;
|
||||
minorMustEqual: boolean;
|
||||
patchBase: number;
|
||||
patchMustEqual: boolean;
|
||||
preRelease: string | null;
|
||||
}
|
||||
|
||||
export interface INormalizedVersion {
|
||||
majorBase: number;
|
||||
majorMustEqual: boolean;
|
||||
minorBase: number;
|
||||
minorMustEqual: boolean;
|
||||
patchBase: number;
|
||||
patchMustEqual: boolean;
|
||||
notBefore: number; /* milliseconds timestamp, or 0 */
|
||||
isMinimum: boolean;
|
||||
}
|
||||
|
||||
const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/;
|
||||
const NOT_BEFORE_REGEXP = /^-(\d{4})(\d{2})(\d{2})$/;
|
||||
|
||||
export function isValidVersionStr(version: string): boolean {
|
||||
version = version.trim();
|
||||
return (version === '*' || VERSION_REGEXP.test(version));
|
||||
}
|
||||
|
||||
export function parseVersion(version: string): IParsedVersion | null {
|
||||
if (!isValidVersionStr(version)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
version = version.trim();
|
||||
|
||||
if (version === '*') {
|
||||
return {
|
||||
hasCaret: false,
|
||||
hasGreaterEquals: false,
|
||||
majorBase: 0,
|
||||
majorMustEqual: false,
|
||||
minorBase: 0,
|
||||
minorMustEqual: false,
|
||||
patchBase: 0,
|
||||
patchMustEqual: false,
|
||||
preRelease: null
|
||||
};
|
||||
}
|
||||
|
||||
const m = version.match(VERSION_REGEXP);
|
||||
if (!m) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
hasCaret: m[1] === '^',
|
||||
hasGreaterEquals: m[1] === '>=',
|
||||
majorBase: m[2] === 'x' ? 0 : parseInt(m[2], 10),
|
||||
majorMustEqual: (m[2] === 'x' ? false : true),
|
||||
minorBase: m[4] === 'x' ? 0 : parseInt(m[4], 10),
|
||||
minorMustEqual: (m[4] === 'x' ? false : true),
|
||||
patchBase: m[6] === 'x' ? 0 : parseInt(m[6], 10),
|
||||
patchMustEqual: (m[6] === 'x' ? false : true),
|
||||
preRelease: m[8] || null
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeVersion(version: IParsedVersion | null): INormalizedVersion | null {
|
||||
if (!version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const majorBase = version.majorBase;
|
||||
const majorMustEqual = version.majorMustEqual;
|
||||
const minorBase = version.minorBase;
|
||||
let minorMustEqual = version.minorMustEqual;
|
||||
const patchBase = version.patchBase;
|
||||
let patchMustEqual = version.patchMustEqual;
|
||||
|
||||
if (version.hasCaret) {
|
||||
if (majorBase === 0) {
|
||||
patchMustEqual = false;
|
||||
} else {
|
||||
minorMustEqual = false;
|
||||
patchMustEqual = false;
|
||||
}
|
||||
}
|
||||
|
||||
let notBefore = 0;
|
||||
if (version.preRelease) {
|
||||
const match = NOT_BEFORE_REGEXP.exec(version.preRelease);
|
||||
if (match) {
|
||||
const [, year, month, day] = match;
|
||||
notBefore = Date.UTC(Number(year), Number(month) - 1, Number(day));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
majorBase: majorBase,
|
||||
majorMustEqual: majorMustEqual,
|
||||
minorBase: minorBase,
|
||||
minorMustEqual: minorMustEqual,
|
||||
patchBase: patchBase,
|
||||
patchMustEqual: patchMustEqual,
|
||||
isMinimum: version.hasGreaterEquals,
|
||||
notBefore,
|
||||
};
|
||||
}
|
|
@ -11,6 +11,7 @@ import { parseTree, findNodeAtLocation, Node as JsonNode, getNodeValue } from 'j
|
|||
import * as MarkdownItType from 'markdown-it';
|
||||
|
||||
import { languages, workspace, Disposable, TextDocument, Uri, Diagnostic, Range, DiagnosticSeverity, Position, env, l10n } from 'vscode';
|
||||
import { INormalizedVersion, normalizeVersion, parseVersion } from './extensionEngineValidation';
|
||||
|
||||
const product = JSON.parse(fs.readFileSync(path.join(env.appRoot, 'product.json'), { encoding: 'utf-8' }));
|
||||
const allowedBadgeProviders: string[] = (product.extensionAllowedBadgeProviders || []).map((s: string) => s.toLowerCase());
|
||||
|
@ -33,6 +34,7 @@ const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs requ
|
|||
const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team.");
|
||||
const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension.");
|
||||
const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations.");
|
||||
const bumpEngineForImplicitActivationEvents = l10n.t("This activation event can be removed for extensions targeting engine version ^1.75 as VS Code will generate these automatically from your package.json contribution declarations.");
|
||||
const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance.");
|
||||
|
||||
enum Context {
|
||||
|
@ -52,6 +54,7 @@ interface PackageJsonInfo {
|
|||
hasHttpsRepository: boolean;
|
||||
repository: Uri;
|
||||
implicitActivationEvents: Set<string> | undefined;
|
||||
engineVersion: INormalizedVersion | null;
|
||||
}
|
||||
|
||||
export class ExtensionLinter {
|
||||
|
@ -156,16 +159,18 @@ export class ExtensionLinter {
|
|||
if (activationEventsNode?.type === 'array' && activationEventsNode.children) {
|
||||
for (const activationEventNode of activationEventsNode.children) {
|
||||
const activationEvent = getNodeValue(activationEventNode);
|
||||
const isImplicitActivationSupported = info.engineVersion && info.engineVersion?.majorBase >= 1 && info.engineVersion?.minorBase >= 75;
|
||||
// Redundant Implicit Activation
|
||||
if (info.implicitActivationEvents?.has(activationEvent) && redundantImplicitActivationEventPrefixes.some((prefix) => activationEvent.startsWith(prefix))) {
|
||||
const start = document.positionAt(activationEventNode.offset);
|
||||
const end = document.positionAt(activationEventNode.offset + activationEventNode.length);
|
||||
diagnostics.push(new Diagnostic(new Range(start, end), redundantImplicitActivationEvent, DiagnosticSeverity.Warning));
|
||||
const message = isImplicitActivationSupported ? redundantImplicitActivationEvent : bumpEngineForImplicitActivationEvents;
|
||||
diagnostics.push(new Diagnostic(new Range(start, end), message, isImplicitActivationSupported ? DiagnosticSeverity.Warning : DiagnosticSeverity.Information));
|
||||
}
|
||||
|
||||
// Reserved Implicit Activation
|
||||
for (const implicitActivationEventPrefix of reservedImplicitActivationEventPrefixes) {
|
||||
if (activationEvent.startsWith(implicitActivationEventPrefix)) {
|
||||
if (isImplicitActivationSupported && activationEvent.startsWith(implicitActivationEventPrefix)) {
|
||||
const start = document.positionAt(activationEventNode.offset);
|
||||
const end = document.positionAt(activationEventNode.offset + activationEventNode.length);
|
||||
diagnostics.push(new Diagnostic(new Range(start, end), implicitActivationEvent, DiagnosticSeverity.Error));
|
||||
|
@ -314,6 +319,7 @@ export class ExtensionLinter {
|
|||
|
||||
private readPackageJsonInfo(folder: Uri, tree: JsonNode | undefined) {
|
||||
const engine = tree && findNodeAtLocation(tree, ['engines', 'vscode']);
|
||||
const parsedEngineVersion = engine?.type === 'string' ? normalizeVersion(parseVersion(engine.value)) : null;
|
||||
const repo = tree && findNodeAtLocation(tree, ['repository', 'url']);
|
||||
const uri = repo && parseUri(repo.value);
|
||||
const activationEvents = tree && parseImplicitActivationEvents(tree);
|
||||
|
@ -322,7 +328,8 @@ export class ExtensionLinter {
|
|||
isExtension: !!(engine && engine.type === 'string'),
|
||||
hasHttpsRepository: !!(repo && repo.type === 'string' && repo.value && uri && uri.scheme.toLowerCase() === 'https'),
|
||||
repository: uri!,
|
||||
implicitActivationEvents: activationEvents
|
||||
implicitActivationEvents: activationEvents,
|
||||
engineVersion: parsedEngineVersion
|
||||
};
|
||||
const str = folder.toString();
|
||||
const oldInfo = this.folderToPackageJsonInfo[str];
|
||||
|
|
Loading…
Reference in a new issue