Add source map for every possible element in the Markdown preview (#134799)

* Update markdown-it and type definitions

* Refresh the source map mechanism in `markdownEngine.ts`
This commit is contained in:
Lemmingh 2021-10-13 07:25:56 +08:00 committed by GitHub
parent d8f67118b4
commit d3ceb35bec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 106 deletions

View file

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const MarkdownIt = require('markdown-it');
const MarkdownIt: typeof import('markdown-it') = require('markdown-it');
import * as DOMPurify from 'dompurify';
import type * as markdownIt from 'markdown-it';
import type * as MarkdownItToken from 'markdown-it/lib/token';
import type { ActivationFunction } from 'vscode-notebook-renderer';
const sanitizerOptions: DOMPurify.Config = {
@ -196,12 +196,12 @@ export const activate: ActivationFunction<void> = (ctx) => {
};
function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
function addNamedHeaderRendering(md: InstanceType<typeof MarkdownIt>): void {
const slugCounter = new Map<string, number>();
const originalHeaderOpen = md.renderer.rules.heading_open;
md.renderer.rules.heading_open = (tokens: markdownIt.Token[], idx: number, options: any, env: any, self: any) => {
const title = tokens[idx + 1].children.reduce((acc: string, t: any) => acc + t.content, '');
md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
let slug = slugFromHeading(title);
if (slugCounter.has(slug)) {
@ -212,13 +212,12 @@ function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
slugCounter.set(slug, 0);
}
tokens[idx].attrs = tokens[idx].attrs || [];
tokens[idx].attrs.push(['id', slug]);
tokens[idx].attrSet('id', slug);
if (originalHeaderOpen) {
return originalHeaderOpen(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};

View file

@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
"moduleResolution": "Node",
"module": "es2020",
"lib": [
"es2018",

View file

@ -352,18 +352,17 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"dompurify": "^2.3.1",
"dompurify": "^2.3.3",
"highlight.js": "^10.4.1",
"markdown-it": "^12.0.3",
"markdown-it": "^12.2.0",
"markdown-it-front-matter": "^0.2.1",
"vscode-extension-telemetry": "0.4.2",
"vscode-nls": "^5.0.0"
},
"devDependencies": {
"@types/dompurify": "^2.2.3",
"@types/highlight.js": "10.1.0",
"@types/dompurify": "^2.3.1",
"@types/lodash.throttle": "^4.1.3",
"@types/markdown-it": "0.0.2",
"@types/markdown-it": "12.2.3",
"@types/vscode-notebook-renderer": "^1.60.0",
"@types/vscode-webview": "^1.57.0",
"lodash.throttle": "^4.1.1"

View file

@ -3,13 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Token } from 'markdown-it';
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider } from '../tableOfContentsProvider';
const rangeLimit = 5000;
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvider {
constructor(
@ -84,10 +88,14 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi
const isStartRegion = (t: string) => /^\s*<!--\s*#?region\b.*-->/.test(t);
const isEndRegion = (t: string) => /^\s*<!--\s*#?endregion\b.*-->/.test(t);
const isRegionMarker = (token: Token) =>
token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap =>
!!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
if (!token.map) {
return false;
}
const isFoldableToken = (token: Token): boolean => {
switch (token.type) {
case 'fence':
case 'list_item_open':

View file

@ -2,11 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Token } from 'markdown-it';
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider, TocEntry } from '../tableOfContentsProvider';
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
}
export default class MarkdownSmartSelect implements vscode.SelectionRangeProvider {
constructor(
@ -96,8 +100,8 @@ function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean,
}
}
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): Token[] {
const enclosingTokens = tokens.filter(token => token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] {
const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
if (enclosingTokens.length === 0) {
return [];
}
@ -105,7 +109,7 @@ function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, p
return sortedTokens;
}
function createBlockRange(block: Token, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
function createBlockRange(block: MarkdownItTokenWithMap, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
if (block.type === 'fence') {
return createFencedRange(block, cursorLine, document, parent);
} else {
@ -144,7 +148,7 @@ function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode
return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection;
}
function createFencedRange(token: Token, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
const startLine = token.map[0];
const endLine = token.map[1] - 1;
const onFenceLine = cursorLine === startLine || cursorLine === endLine;

View file

@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MarkdownIt, Token } from 'markdown-it';
import MarkdownIt = require('markdown-it');
import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
@ -14,11 +15,34 @@ import { WebviewResourceProvider } from './util/resources';
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
interface MarkdownItConfig {
readonly breaks: boolean;
readonly linkify: boolean;
readonly typographer: boolean;
}
/**
* Adds begin line index to the output via the 'data-line' data attribute.
*/
const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => {
// Set the attribute on every possible token.
md.core.ruler.push('source_map_data_attribute', (state): void => {
for (const token of state.tokens) {
if (token.map && token.type !== 'inline') {
token.attrSet('data-line', String(token.map[0]));
token.attrJoin('class', 'code-line');
}
}
});
// The 'html_block' renderer doesn't respect `attrs`. We need to insert a marker.
const originalHtmlBlockRenderer = md.renderer.rules['html_block'];
if (originalHtmlBlockRenderer) {
md.renderer.rules['html_block'] = (tokens, idx, options, env, self) => (
`<div ${self.renderAttrs(tokens[idx])} ></div>\n` +
originalHtmlBlockRenderer(tokens, idx, options, env, self)
);
}
};
/**
* The markdown-it options that we expose in the settings.
*/
type MarkdownItConfig = Readonly<Required<Pick<MarkdownIt.Options, 'breaks' | 'linkify' | 'typographer'>>>;
class TokenCache {
private cachedDocument?: {
@ -85,7 +109,8 @@ export class MarkdownEngine {
private async getEngine(config: MarkdownItConfig): Promise<MarkdownIt> {
if (!this.md) {
this.md = import('markdown-it').then(async markdownIt => {
this.md = (async () => {
const markdownIt = await import('markdown-it');
let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md));
for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) {
@ -111,18 +136,15 @@ export class MarkdownEngine {
alt: ['paragraph', 'reference', 'blockquote', 'list']
});
for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'fence', 'blockquote_open', 'list_item_open']) {
this.addLineNumberRenderer(md, renderName);
}
this.addImageRenderer(md);
this.addFencedRenderer(md);
this.addLinkNormalizer(md);
this.addLinkValidator(md);
this.addNamedHeaders(md);
this.addLinkRenderer(md);
md.use(pluginSourceMap);
return md;
});
})();
}
const md = await this.md!;
@ -170,7 +192,7 @@ export class MarkdownEngine {
};
const html = engine.renderer.render(tokens, {
...(engine as any).options,
...engine.options,
...config
}, env);
@ -199,26 +221,9 @@ export class MarkdownEngine {
};
}
private addLineNumberRenderer(md: MarkdownIt, ruleName: string): void {
const original = md.renderer.rules[ruleName];
md.renderer.rules[ruleName] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
const token = tokens[idx];
if (token.map && token.map.length) {
token.attrSet('data-line', token.map[0] + '');
token.attrJoin('class', 'code-line');
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
}
};
}
private addImageRenderer(md: MarkdownIt): void {
const original = md.renderer.rules.image;
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: RenderEnv, self: any) => {
md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => {
const token = tokens[idx];
token.attrJoin('class', 'loading');
@ -237,20 +242,24 @@ export class MarkdownEngine {
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};
}
private addFencedRenderer(md: MarkdownIt): void {
const original = md.renderer.rules['fenced'];
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options, env, self) => {
const token = tokens[idx];
if (token.map && token.map.length) {
token.attrJoin('class', 'hljs');
}
return original(tokens, idx, options, env, self);
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options);
}
};
}
@ -282,8 +291,8 @@ export class MarkdownEngine {
private addNamedHeaders(md: MarkdownIt): void {
const original = md.renderer.rules.heading_open;
md.renderer.rules.heading_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
const title = tokens[idx + 1].children.reduce((acc: string, t: any) => acc + t.content, '');
md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => {
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
let slug = this.slugifier.fromHeading(title);
if (this._slugCount.has(slug.value)) {
@ -294,30 +303,31 @@ export class MarkdownEngine {
this._slugCount.set(slug.value, 0);
}
tokens[idx].attrs = tokens[idx].attrs || [];
tokens[idx].attrs.push(['id', slug.value]);
tokens[idx].attrSet('id', slug.value);
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
return self.renderToken(tokens, idx, options);
}
};
}
private addLinkRenderer(md: MarkdownIt): void {
const old_render = md.renderer.rules.link_open || ((tokens: Token[], idx: number, options: any, _env: any, self: any) => {
return self.renderToken(tokens, idx, options);
});
const original = md.renderer.rules.link_open;
md.renderer.rules.link_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
md.renderer.rules.link_open = (tokens: Token[], idx: number, options, env, self) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const href = token.attrs[hrefIndex][1];
token.attrPush(['data-href', href]);
const href = token.attrGet('href');
// A string, including empty string, may be `href`.
if (typeof href === 'string') {
token.attrSet('data-href', href);
}
if (original) {
return original(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options);
}
return old_render(tokens, idx, options, env, self);
};
}
@ -366,7 +376,7 @@ export class MarkdownEngine {
}
}
async function getMarkdownOptions(md: () => MarkdownIt) {
async function getMarkdownOptions(md: () => MarkdownIt): Promise<MarkdownIt.Options> {
const hljs = await import('highlight.js');
return {
html: true,

View file

@ -2,19 +2,17 @@
# yarn lockfile v1
"@types/dompurify@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.2.3.tgz#6e89677a07902ac1b6821c345f34bd85da239b08"
integrity sha512-CLtc2mZK8+axmrz1JqtpklO/Kvn38arGc8o1l3UVopZaXXuer9ONdZwJ/9f226GrhRLtUmLr9WrvZsRSNpS8og==
"@types/dompurify@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.1.tgz#2934adcd31c4e6b02676f9c22f9756e5091c04dd"
integrity sha512-YJth9qa0V/E6/XPH1Jq4BC8uCMmO8V1fKWn8PCvuZcAhMn7q0ez9LW6naQT04UZzjFfAPhyRMZmI2a2rbMlEFA==
dependencies:
"@types/trusted-types" "*"
"@types/highlight.js@10.1.0":
version "10.1.0"
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-10.1.0.tgz#89bb0c202997d7a90a07bd2ec1f7d00c56bb90b4"
integrity sha512-77hF2dGBsOgnvZll1vymYiNUtqJ8cJfXPD6GG/2M0aLRc29PkvB7Au6sIDjIEFcSICBhCh2+Pyq6WSRS7LUm6A==
dependencies:
highlight.js "*"
"@types/linkify-it@*":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
"@types/lodash.throttle@^4.1.3":
version "4.1.3"
@ -28,10 +26,18 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80"
integrity sha512-ufQcVg4daO8xQ5kopxRHanqFdL4AI7ondQkV+2f+7mz3gvp0LkBx2zBRC6hfs3T87mzQFmf5Fck7Fi145Ul6NQ==
"@types/markdown-it@0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
"@types/markdown-it@12.2.3":
version "12.2.3"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
dependencies:
"@types/linkify-it" "*"
"@types/mdurl" "*"
"@types/mdurl@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
"@types/trusted-types@*":
version "2.0.2"
@ -53,25 +59,25 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
dompurify@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.1.tgz#a47059ca21fd1212d3c8f71fdea6943b8bfbdf6a"
integrity sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw==
dompurify@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c"
integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==
entities@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
highlight.js@*, highlight.js@^10.4.1:
highlight.js@^10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0"
integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==
linkify-it@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==
version "3.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
dependencies:
uc.micro "^1.0.1"
@ -85,10 +91,10 @@ markdown-it-front-matter@^0.2.1:
resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.2.1.tgz#dca49a827bb3cebb0528452c1d87dff276eb28dc"
integrity sha512-ydUIqlKfDscRpRUTRcA3maeeUKn3Cl5EaKZSA+I/f0KOGCBurW7e+bbz59sxqkC3FA9Q2S2+t4mpkH9T0BCM6A==
markdown-it@^12.0.3:
version "12.0.3"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.3.tgz#8d1e47daf1d716d63610495eb93f6665573e4abe"
integrity sha512-M57RsMv+QQmJHz1yCu0gTJRMx/LlxRPtrrw+2kb/CpDVK/graCmWO0qfNnz/SE1FCNdyq3pkMMZ+itTnyT/YGA==
markdown-it@^12.2.0:
version "12.2.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db"
integrity sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==
dependencies:
argparse "^2.0.1"
entities "~2.1.0"
@ -101,15 +107,10 @@ mdurl@^1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
uc.micro@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI=
uc.micro@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
vscode-extension-telemetry@0.4.2:
version "0.4.2"