mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 10:38:59 +01:00
Fixes #64918: Remove old task conversion code
This commit is contained in:
@@ -31,11 +31,10 @@ import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO, TaskSetDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { ITreeItem, IRevealOptions } from 'vs/workbench/common/views';
|
||||
import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder';
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IRPCProtocol, ProxyIdentifier, createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
@@ -933,7 +932,7 @@ export interface ExtHostSCMShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Promise<TaskSet>;
|
||||
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<TaskSetDTO>;
|
||||
$onDidStartTask(execution: TaskExecutionDTO): void;
|
||||
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
|
||||
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { win32 } from 'vs/base/node/processes';
|
||||
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as tasks from 'vs/workbench/parts/tasks/common/tasks';
|
||||
|
||||
import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
@@ -22,7 +20,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO,
|
||||
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO
|
||||
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO
|
||||
} from '../shared/tasks';
|
||||
import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
@@ -30,434 +28,6 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
/*
|
||||
namespace ProblemPattern {
|
||||
export function from(value: vscode.ProblemPattern | vscode.MultiLineProblemPattern): Problems.ProblemPattern | Problems.MultiLineProblemPattern {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
let result: Problems.ProblemPattern[] = [];
|
||||
for (let pattern of value) {
|
||||
let converted = fromSingle(pattern);
|
||||
if (!converted) {
|
||||
return undefined;
|
||||
}
|
||||
result.push(converted);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return fromSingle(value);
|
||||
}
|
||||
}
|
||||
|
||||
function copyProperty(target: Problems.ProblemPattern, source: vscode.ProblemPattern, tk: keyof Problems.ProblemPattern) {
|
||||
let sk: keyof vscode.ProblemPattern = tk;
|
||||
let value = source[sk];
|
||||
if (typeof value === 'number') {
|
||||
target[tk] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(value: number, defaultValue: number): number {
|
||||
if (value !== void 0 && value === null) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function fromSingle(problemPattern: vscode.ProblemPattern): Problems.ProblemPattern {
|
||||
if (problemPattern === void 0 || problemPattern === null || !(problemPattern.regexp instanceof RegExp)) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.ProblemPattern = {
|
||||
regexp: problemPattern.regexp
|
||||
};
|
||||
copyProperty(result, problemPattern, 'file');
|
||||
copyProperty(result, problemPattern, 'location');
|
||||
copyProperty(result, problemPattern, 'line');
|
||||
copyProperty(result, problemPattern, 'character');
|
||||
copyProperty(result, problemPattern, 'endLine');
|
||||
copyProperty(result, problemPattern, 'endCharacter');
|
||||
copyProperty(result, problemPattern, 'severity');
|
||||
copyProperty(result, problemPattern, 'code');
|
||||
copyProperty(result, problemPattern, 'message');
|
||||
if (problemPattern.loop === true || problemPattern.loop === false) {
|
||||
result.loop = problemPattern.loop;
|
||||
}
|
||||
if (result.location) {
|
||||
result.file = getValue(result.file, 1);
|
||||
result.message = getValue(result.message, 0);
|
||||
} else {
|
||||
result.file = getValue(result.file, 1);
|
||||
result.line = getValue(result.line, 2);
|
||||
result.character = getValue(result.character, 3);
|
||||
result.message = getValue(result.message, 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ApplyTo {
|
||||
export function from(value: vscode.ApplyToKind): Problems.ApplyToKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return Problems.ApplyToKind.allDocuments;
|
||||
}
|
||||
switch (value) {
|
||||
case types.ApplyToKind.OpenDocuments:
|
||||
return Problems.ApplyToKind.openDocuments;
|
||||
case types.ApplyToKind.ClosedDocuments:
|
||||
return Problems.ApplyToKind.closedDocuments;
|
||||
}
|
||||
return Problems.ApplyToKind.allDocuments;
|
||||
}
|
||||
}
|
||||
|
||||
namespace FileLocation {
|
||||
export function from(value: vscode.FileLocationKind | string): { kind: Problems.FileLocationKind; prefix?: string } {
|
||||
if (value === void 0 || value === null) {
|
||||
return { kind: Problems.FileLocationKind.Auto };
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return { kind: Problems.FileLocationKind.Relative, prefix: value };
|
||||
}
|
||||
switch (value) {
|
||||
case types.FileLocationKind.Absolute:
|
||||
return { kind: Problems.FileLocationKind.Absolute };
|
||||
case types.FileLocationKind.Relative:
|
||||
return { kind: Problems.FileLocationKind.Relative, prefix: '${workspaceFolder}' };
|
||||
}
|
||||
return { kind: Problems.FileLocationKind.Auto };
|
||||
}
|
||||
}
|
||||
|
||||
namespace WatchingPattern {
|
||||
export function from(value: RegExp | vscode.BackgroundPattern): Problems.WatchingPattern {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return { regexp: value };
|
||||
}
|
||||
if (!(value.regexp instanceof RegExp)) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.WatchingPattern = {
|
||||
regexp: value.regexp
|
||||
};
|
||||
if (typeof value.file === 'number') {
|
||||
result.file = value.file;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace BackgroundMonitor {
|
||||
export function from(value: vscode.BackgroundMonitor): Problems.WatchingMatcher {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: Problems.WatchingMatcher = {
|
||||
activeOnStart: !!value.activeOnStart,
|
||||
beginsPattern: WatchingPattern.from(value.beginsPattern),
|
||||
endsPattern: WatchingPattern.from(value.endsPattern)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ProblemMatcher {
|
||||
export function from(values: (string | vscode.ProblemMatcher)[]): (string | Problems.ProblemMatcher)[] {
|
||||
if (values === void 0 || values === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: (string | Problems.ProblemMatcher)[] = [];
|
||||
for (let value of values) {
|
||||
let converted = typeof value === 'string' ? value : fromSingle(value);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(problemMatcher: vscode.ProblemMatcher): Problems.ProblemMatcher {
|
||||
if (problemMatcher === void 0 || problemMatcher === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let location = FileLocation.from(problemMatcher.fileLocation);
|
||||
let result: Problems.ProblemMatcher = {
|
||||
owner: typeof problemMatcher.owner === 'string' ? problemMatcher.owner : UUID.generateUuid(),
|
||||
applyTo: ApplyTo.from(problemMatcher.applyTo),
|
||||
fileLocation: location.kind,
|
||||
filePrefix: location.prefix,
|
||||
pattern: ProblemPattern.from(problemMatcher.pattern),
|
||||
severity: fromDiagnosticSeverity(problemMatcher.severity),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
namespace TaskRevealKind {
|
||||
export function from(value: vscode.TaskRevealKind): tasks.RevealKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return tasks.RevealKind.Always;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskRevealKind.Silent:
|
||||
return tasks.RevealKind.Silent;
|
||||
case types.TaskRevealKind.Never:
|
||||
return tasks.RevealKind.Never;
|
||||
}
|
||||
return tasks.RevealKind.Always;
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskPanelKind {
|
||||
export function from(value: vscode.TaskPanelKind): tasks.PanelKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return tasks.PanelKind.Shared;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskPanelKind.Dedicated:
|
||||
return tasks.PanelKind.Dedicated;
|
||||
case types.TaskPanelKind.New:
|
||||
return tasks.PanelKind.New;
|
||||
default:
|
||||
return tasks.PanelKind.Shared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace PresentationOptions {
|
||||
export function from(value: vscode.TaskPresentationOptions): tasks.PresentationOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return { reveal: tasks.RevealKind.Always, echo: true, focus: false, panel: tasks.PanelKind.Shared, showReuseMessage: true, clear: false };
|
||||
}
|
||||
return {
|
||||
reveal: TaskRevealKind.from(value.reveal),
|
||||
echo: value.echo === void 0 ? true : !!value.echo,
|
||||
focus: !!value.focus,
|
||||
panel: TaskPanelKind.from(value.panel),
|
||||
showReuseMessage: value.showReuseMessage === void 0 ? true : !!value.showReuseMessage,
|
||||
clear: value.clear === void 0 ? false : !!value.clear,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace Strings {
|
||||
export function from(value: string[]): string[] {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
for (let element of value) {
|
||||
if (typeof element !== 'string') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
namespace CommandOptions {
|
||||
function isShellConfiguration(value: any): value is { executable: string; shellArgs?: string[] } {
|
||||
return value && typeof value.executable === 'string';
|
||||
}
|
||||
export function from(value: vscode.ShellExecutionOptions | vscode.ProcessExecutionOptions): tasks.CommandOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: tasks.CommandOptions = {
|
||||
};
|
||||
if (typeof value.cwd === 'string') {
|
||||
result.cwd = value.cwd;
|
||||
}
|
||||
if (value.env) {
|
||||
result.env = Object.create(null);
|
||||
Object.keys(value.env).forEach(key => {
|
||||
let envValue = value.env[key];
|
||||
if (typeof envValue === 'string') {
|
||||
result.env[key] = envValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isShellConfiguration(value)) {
|
||||
result.shell = ShellConfiguration.from(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShellQuoteOptions {
|
||||
export function from(value: vscode.ShellQuotingOptions): tasks.ShellQuotingOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
escape: value.escape,
|
||||
strong: value.strong,
|
||||
weak: value.strong
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShellConfiguration {
|
||||
export function from(value: { executable?: string, shellArgs?: string[], quotes?: vscode.ShellQuotingOptions }): tasks.ShellConfiguration {
|
||||
if (value === void 0 || value === null || !value.executable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: tasks.ShellConfiguration = {
|
||||
executable: value.executable,
|
||||
args: Strings.from(value.shellArgs),
|
||||
quoting: ShellQuoteOptions.from(value.quotes)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShellString {
|
||||
export function from(value: (string | vscode.ShellQuotedString)[]): tasks.CommandString[] {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return value.slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tasks {
|
||||
|
||||
export function from(tasks: vscode.Task[], rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): tasks.ContributedTask[] {
|
||||
if (tasks === void 0 || tasks === null) {
|
||||
return [];
|
||||
}
|
||||
let result: tasks.ContributedTask[] = [];
|
||||
for (let task of tasks) {
|
||||
let converted = fromSingle(task, rootFolder, extension);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(task: vscode.Task, rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): tasks.ContributedTask {
|
||||
if (typeof task.name !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let command: tasks.CommandConfiguration;
|
||||
let execution = task.execution;
|
||||
if (execution instanceof types.ProcessExecution) {
|
||||
command = getProcessCommand(execution);
|
||||
} else if (execution instanceof types.ShellExecution) {
|
||||
command = getShellCommand(execution);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
if (command === void 0) {
|
||||
return undefined;
|
||||
}
|
||||
command.presentation = PresentationOptions.from(task.presentationOptions);
|
||||
|
||||
let taskScope: types.TaskScope.Global | types.TaskScope.Workspace | vscode.WorkspaceFolder | undefined = task.scope;
|
||||
let workspaceFolder: vscode.WorkspaceFolder | undefined;
|
||||
let scope: tasks.TaskScope;
|
||||
// For backwards compatibility
|
||||
if (taskScope === void 0) {
|
||||
scope = tasks.TaskScope.Folder;
|
||||
workspaceFolder = rootFolder;
|
||||
} else if (taskScope === types.TaskScope.Global) {
|
||||
scope = tasks.TaskScope.Global;
|
||||
} else if (taskScope === types.TaskScope.Workspace) {
|
||||
scope = tasks.TaskScope.Workspace;
|
||||
} else {
|
||||
scope = tasks.TaskScope.Folder;
|
||||
workspaceFolder = taskScope;
|
||||
}
|
||||
let source: tasks.ExtensionTaskSource = {
|
||||
kind: tasks.TaskSourceKind.Extension,
|
||||
label: typeof task.source === 'string' ? task.source : extension.name,
|
||||
extension: extension.id,
|
||||
scope: scope,
|
||||
workspaceFolder: undefined
|
||||
};
|
||||
// We can't transfer a workspace folder object from the extension host to main since they differ
|
||||
// in shape and we don't have backwards converting function. So transfer the URI and resolve the
|
||||
// workspace folder on the main side.
|
||||
(source as any as tasks.ExtensionTaskSourceTransfer).__workspaceFolder = workspaceFolder ? workspaceFolder.uri as URI : undefined;
|
||||
(source as any as tasks.ExtensionTaskSourceTransfer).__definition = task.definition;
|
||||
let label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
|
||||
// The definition id will be prefix on the main side since we compute it there.
|
||||
let id = `${extension.id}`;
|
||||
let result: tasks.ContributedTask = {
|
||||
_id: id,
|
||||
_source: source,
|
||||
_label: label,
|
||||
type: task.definition.type,
|
||||
defines: undefined,
|
||||
name: task.name,
|
||||
identifier: label,
|
||||
group: task.group ? (task.group as types.TaskGroup).id : undefined,
|
||||
command: command,
|
||||
isBackground: !!task.isBackground,
|
||||
problemMatchers: task.problemMatchers.slice(),
|
||||
hasDefinedMatchers: (task as types.Task).hasDefinedMatchers,
|
||||
runOptions: (<vscode.Task>task).runOptions ? (<vscode.Task>task).runOptions : { reevaluateOnRerun: true },
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function getProcessCommand(value: vscode.ProcessExecution): tasks.CommandConfiguration {
|
||||
if (typeof value.process !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.process,
|
||||
args: Strings.from(value.args),
|
||||
runtime: tasks.RuntimeType.Process,
|
||||
suppressTaskName: true,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
result.options = CommandOptions.from(value.options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getShellCommand(value: vscode.ShellExecution): tasks.CommandConfiguration {
|
||||
if (value.args) {
|
||||
if (typeof value.command !== 'string' && typeof value.command.value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.command,
|
||||
args: ShellString.from(value.args),
|
||||
runtime: tasks.RuntimeType.Shell,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
result.options = CommandOptions.from(value.options);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (typeof value.commandLine !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.commandLine,
|
||||
runtime: tasks.RuntimeType.Shell,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
result.options = CommandOptions.from(value.options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskDefinitionDTO {
|
||||
export function from(value: vscode.TaskDefinition): TaskDefinitionDTO {
|
||||
if (value === void 0 || value === null) {
|
||||
@@ -593,6 +163,20 @@ namespace TaskHandleDTO {
|
||||
|
||||
namespace TaskDTO {
|
||||
|
||||
export function fromMany(tasks: vscode.Task[], extension: IExtensionDescription): TaskDTO[] {
|
||||
if (tasks === void 0 || tasks === null) {
|
||||
return [];
|
||||
}
|
||||
let result: TaskDTO[] = [];
|
||||
for (let task of tasks) {
|
||||
let converted = from(task, extension);
|
||||
if (converted) {
|
||||
result.push(converted);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function from(value: vscode.Task, extension: IExtensionDescription): TaskDTO {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
@@ -865,7 +449,7 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Promise<tasks.TaskSet> {
|
||||
public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<TaskSetDTO> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return Promise.reject(new Error('no handler found'));
|
||||
@@ -880,9 +464,8 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`);
|
||||
}
|
||||
}
|
||||
let workspaceFolders = this._workspaceService.getWorkspaceFolders();
|
||||
return {
|
||||
tasks: Tasks.from(sanitized, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
|
||||
tasks: TaskDTO.fromMany(sanitized, handler.extension),
|
||||
extension: handler.extension
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user