From 93bea490b0e6117e83d5e38db0a98d453f69b1bd Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 6 Jan 2020 13:37:49 -0800 Subject: [PATCH 1/3] Add call hierarchy support for TypeScript 3.8 --- extensions/package.json | 2 +- .../src/features/callHierarchy.ts | 72 ++++++++++++++++++ .../src/languageProvider.ts | 1 + .../src/protocol.const.ts | 1 + .../src/typescriptService.ts | 3 + .../src/utils/typeConverters.ts | 75 +++++++++++++++++++ extensions/yarn.lock | 8 +- 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 extensions/typescript-language-features/src/features/callHierarchy.ts diff --git a/extensions/package.json b/extensions/package.json index 65d512cb8a579..7b6c8cd9685d9 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.7.3" + "typescript": "3.8.0-dev.20200104" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts new file mode 100644 index 0000000000000..9d8540b9475b7 --- /dev/null +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import * as typeConverters from '../utils/typeConverters'; + +class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { + public constructor( + private readonly client: ITypeScriptServiceClient) { } + + public async prepareCallHierarchy( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { + const filepath = this.client.toOpenedFilePath(document); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + const response = await this.client.execute('prepareCallHierarchy', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return Array.isArray(response.body) + ? response.body.map(typeConverters.CallHierarchyItem.fromProtocolCallHierarchyItem) + : typeConverters.CallHierarchyItem.fromProtocolCallHierarchyItem(response.body); + } + + public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(typeConverters.CallHierarchyIncomingCall.fromProtocolCallHierchyIncomingCall); + } + + public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(typeConverters.CallHierarchyOutgoingCall.fromProtocolCallHierchyOutgoingCall); + } +} + +export function register( + selector: vscode.DocumentSelector, + client: ITypeScriptServiceClient +) { + return vscode.languages.registerCallHierarchyProvider(selector, + new TypeScriptCallHierarchySupport(client)); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 0d434f24ff656..494ea90230a19 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable { import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), + import('./features/callHierarchy').then(provider => this._register(provider.register(selector, this.client))), ]); } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index 7a6a7d7023569..a44c175f295fc 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -33,6 +33,7 @@ export class Kind { public static readonly warning = 'warning'; public static readonly string = 'string'; public static readonly parameter = 'parameter'; + public static readonly typeParameter = 'type parameter'; } diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 3b5a139cd0af2..043284c124473 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -58,6 +58,9 @@ interface StandardTsServerRequests { 'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse]; 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]; 'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response]; + 'prepareCallHierarchy': [Proto.FileLocationRequestArgs, Proto.PrepareCallHierarchyResponse]; + 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]; + 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index 37947b38810d5..c09674a053361 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -7,14 +7,21 @@ * Helpers for converting FROM vscode types TO ts types */ +import * as path from 'path'; import * as vscode from 'vscode'; import * as Proto from '../protocol'; +import * as PConst from '../protocol.const'; import { ITypeScriptServiceClient } from '../typescriptService'; export namespace Range { export const fromTextSpan = (span: Proto.TextSpan): vscode.Range => fromLocations(span.start, span.end); + export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({ + start: Position.toLocation(range.start), + end: Position.toLocation(range.end) + }); + export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range => new vscode.Range( Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), @@ -90,3 +97,71 @@ export namespace WorkspaceEdit { return workspaceEdit; } } + +export namespace SymbolKind { + export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) { + switch (kind) { + case PConst.Kind.module: return vscode.SymbolKind.Module; + case PConst.Kind.class: return vscode.SymbolKind.Class; + case PConst.Kind.enum: return vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember; + case PConst.Kind.interface: return vscode.SymbolKind.Interface; + case PConst.Kind.indexSignature: return vscode.SymbolKind.Method; + case PConst.Kind.callSignature: return vscode.SymbolKind.Method; + case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; + case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.variable: return vscode.SymbolKind.Variable; + case PConst.Kind.let: return vscode.SymbolKind.Variable; + case PConst.Kind.const: return vscode.SymbolKind.Variable; + case PConst.Kind.localVariable: return vscode.SymbolKind.Variable; + case PConst.Kind.alias: return vscode.SymbolKind.Variable; + case PConst.Kind.function: return vscode.SymbolKind.Function; + case PConst.Kind.localFunction: return vscode.SymbolKind.Function; + case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor; + case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor; + case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter; + case PConst.Kind.string: return vscode.SymbolKind.String; + default: return vscode.SymbolKind.Variable; + } + } +} + +export namespace CallHierarchyItem { + function isSourceFileItem(item: Proto.CallHierarchyItem) { + return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0; + } + + export function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem { + const useFileName = isSourceFileItem(item); + const name = useFileName ? path.basename(item.file) : item.name; + const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : ''; + return new vscode.CallHierarchyItem( + SymbolKind.fromProtocolScriptElementKind(item.kind), + name, + detail, + vscode.Uri.file(item.file), + Range.fromTextSpan(item.span), + Range.fromTextSpan(item.selectionSpan) + ); + } +} + +export namespace CallHierarchyIncomingCall { + export function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { + return new vscode.CallHierarchyIncomingCall( + CallHierarchyItem.fromProtocolCallHierarchyItem(item.from), + item.fromSpans.map(Range.fromTextSpan) + ); + } +} + +export namespace CallHierarchyOutgoingCall { + export function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { + return new vscode.CallHierarchyOutgoingCall( + CallHierarchyItem.fromProtocolCallHierarchyItem(item.to), + item.fromSpans.map(Range.fromTextSpan) + ); + } +} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index a8eb51df65734..49759f951bba5 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.7.3: - version "3.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" - integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== +typescript@3.8.0-dev.20200104: + version "3.8.0-dev.20200104" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" + integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== From 4c2ae008fb149e2ec5fed74e31fe3e9ef75386e0 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 6 Jan 2020 13:59:21 -0800 Subject: [PATCH 2/3] Add version dependent registration for call hierarchy provider --- .../src/features/callHierarchy.ts | 8 ++++++-- extensions/typescript-language-features/src/utils/api.ts | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts index 9d8540b9475b7..1b9d0795df43b 100644 --- a/extensions/typescript-language-features/src/features/callHierarchy.ts +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -6,8 +6,11 @@ import * as vscode from 'vscode'; import { ITypeScriptServiceClient } from '../typescriptService'; import * as typeConverters from '../utils/typeConverters'; +import API from '../utils/api'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { + public static readonly minVersion = API.v380; public constructor( private readonly client: ITypeScriptServiceClient) { } @@ -67,6 +70,7 @@ export function register( selector: vscode.DocumentSelector, client: ITypeScriptServiceClient ) { - return vscode.languages.registerCallHierarchyProvider(selector, - new TypeScriptCallHierarchySupport(client)); + return new VersionDependentRegistration(client, TypeScriptCallHierarchySupport.minVersion, + () => vscode.languages.registerCallHierarchyProvider(selector, + new TypeScriptCallHierarchySupport(client))); } diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index c30bee16c7e1c..0fa41cb01a0b8 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -31,6 +31,7 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0'); public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); + public static readonly v380 = API.fromSimpleString('3.8.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); From 870edea67b5924035e4fb163cc4e431678886031 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Jan 2020 12:28:35 -0800 Subject: [PATCH 3/3] Revert TS version, PR feedback --- extensions/package.json | 2 +- .../src/features/callHierarchy.ts | 43 +++++++++++++-- .../src/protocol.d.ts | 52 +++++++++++++++++++ .../src/utils/typeConverters.ts | 39 -------------- extensions/yarn.lock | 8 +-- 5 files changed, 96 insertions(+), 48 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 7b6c8cd9685d9..65d512cb8a579 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.8.0-dev.20200104" + "typescript": "3.7.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts index 1b9d0795df43b..b5b4501d2adce 100644 --- a/extensions/typescript-language-features/src/features/callHierarchy.ts +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -8,6 +8,9 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import * as typeConverters from '../utils/typeConverters'; import API from '../utils/api'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; +import * as Proto from '../protocol'; +import * as path from 'path'; +import * as PConst from '../protocol.const'; class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { public static readonly minVersion = API.v380; @@ -31,8 +34,8 @@ class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { } return Array.isArray(response.body) - ? response.body.map(typeConverters.CallHierarchyItem.fromProtocolCallHierarchyItem) - : typeConverters.CallHierarchyItem.fromProtocolCallHierarchyItem(response.body); + ? response.body.map(fromProtocolCallHierarchyItem) + : fromProtocolCallHierarchyItem(response.body); } public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -47,7 +50,7 @@ class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { return undefined; } - return response.body.map(typeConverters.CallHierarchyIncomingCall.fromProtocolCallHierchyIncomingCall); + return response.body.map(fromProtocolCallHierchyIncomingCall); } public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -62,10 +65,42 @@ class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { return undefined; } - return response.body.map(typeConverters.CallHierarchyOutgoingCall.fromProtocolCallHierchyOutgoingCall); + return response.body.map(fromProtocolCallHierchyOutgoingCall); } } +function isSourceFileItem(item: Proto.CallHierarchyItem) { + return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0; +} + +function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem { + const useFileName = isSourceFileItem(item); + const name = useFileName ? path.basename(item.file) : item.name; + const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : ''; + return new vscode.CallHierarchyItem( + typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind), + name, + detail, + vscode.Uri.file(item.file), + typeConverters.Range.fromTextSpan(item.span), + typeConverters.Range.fromTextSpan(item.selectionSpan) + ); +} + +function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { + return new vscode.CallHierarchyIncomingCall( + fromProtocolCallHierarchyItem(item.from), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + +function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { + return new vscode.CallHierarchyOutgoingCall( + fromProtocolCallHierarchyItem(item.to), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + export function register( selector: vscode.DocumentSelector, client: ITypeScriptServiceClient diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 6e926eb8d7ee2..31bc4746cb2ee 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,2 +1,54 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; + +declare module "typescript/lib/protocol" { + const enum CommandTypes { + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + } + + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface PrepareCallHierarchyRequest extends FileLocationRequest { + command: CommandTypes.PrepareCallHierarchy; + } + + interface PrepareCallHierarchyResponse extends Response { + readonly body: CallHierarchyItem | CallHierarchyItem[]; + } + + interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyIncomingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyIncomingCallsResponse extends Response { + readonly body: CallHierarchyIncomingCall[]; + } + + interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyOutgoingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyOutgoingCallsResponse extends Response { + readonly body: CallHierarchyOutgoingCall[]; + } +} diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index c09674a053361..2ccf306885ff9 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -7,7 +7,6 @@ * Helpers for converting FROM vscode types TO ts types */ -import * as path from 'path'; import * as vscode from 'vscode'; import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; @@ -127,41 +126,3 @@ export namespace SymbolKind { } } } - -export namespace CallHierarchyItem { - function isSourceFileItem(item: Proto.CallHierarchyItem) { - return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0; - } - - export function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem { - const useFileName = isSourceFileItem(item); - const name = useFileName ? path.basename(item.file) : item.name; - const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : ''; - return new vscode.CallHierarchyItem( - SymbolKind.fromProtocolScriptElementKind(item.kind), - name, - detail, - vscode.Uri.file(item.file), - Range.fromTextSpan(item.span), - Range.fromTextSpan(item.selectionSpan) - ); - } -} - -export namespace CallHierarchyIncomingCall { - export function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { - return new vscode.CallHierarchyIncomingCall( - CallHierarchyItem.fromProtocolCallHierarchyItem(item.from), - item.fromSpans.map(Range.fromTextSpan) - ); - } -} - -export namespace CallHierarchyOutgoingCall { - export function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { - return new vscode.CallHierarchyOutgoingCall( - CallHierarchyItem.fromProtocolCallHierarchyItem(item.to), - item.fromSpans.map(Range.fromTextSpan) - ); - } -} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 49759f951bba5..a8eb51df65734 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.8.0-dev.20200104: - version "3.8.0-dev.20200104" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" - integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== +typescript@3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==