mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 07:00:11 +01:00
Use JavaScript worker for interpolation IntelliSense
Inside ${...} interpolations, the package now uses Monaco's JavaScript
language service worker for full IntelliSense:
- getCompletionsAtPosition: Property access, method completions
- getQuickInfoAtPosition: Type info and documentation on hover
- getSignatureHelpItems: Function parameter hints
This allows users to configure the context using setExtraLibs:
```typescript
monaco.languages.typescript.javascriptDefaults.setExtraLibs([{
content: `
declare const config: { debug: boolean; port: number };
declare const env: string;
`,
filePath: 'context.d.ts'
}]);
```
Then typing `config.` inside ${...} will show `debug` and `port` completions
with proper types.
This commit is contained in:
parent
59eafca1f2
commit
a96c488593
1 changed files with 284 additions and 92 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
/*---------------------------------------------------------------------------------------------
|
||||||
* Monaco JSON Interpolation
|
* Monaco JSON Interpolation
|
||||||
* Standalone add-on for Monaco Editor providing JSON with ${...} variable interpolation
|
* Standalone add-on for Monaco Editor providing JSON with ${...} variable interpolation
|
||||||
* Integrates with Monaco's built-in JSON language service for full functionality
|
* Integrates with Monaco's built-in JSON and JavaScript language services
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
|
|
@ -154,7 +154,6 @@ type JSONWorker = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getJSONWorker(resource: monaco.Uri): Promise<JSONWorker | null> {
|
async function getJSONWorker(resource: monaco.Uri): Promise<JSONWorker | null> {
|
||||||
// Access Monaco's built-in JSON language service
|
|
||||||
const json = (monaco.languages as any).json;
|
const json = (monaco.languages as any).json;
|
||||||
if (!json || !json.getWorker) {
|
if (!json || !json.getWorker) {
|
||||||
console.warn('monaco-json-interpolation: JSON language service not available');
|
console.warn('monaco-json-interpolation: JSON language service not available');
|
||||||
|
|
@ -171,31 +170,115 @@ async function getJSONWorker(resource: monaco.Uri): Promise<JSONWorker | null> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Check if position is inside interpolation ---
|
// --- Helper to get JavaScript worker ---
|
||||||
|
|
||||||
function isInsideInterpolation(model: monaco.editor.ITextModel, position: monaco.Position): boolean {
|
type JSWorker = {
|
||||||
const textUntilPosition = model.getValueInRange({
|
getCompletionsAtPosition(uri: string, offset: number): Promise<any>;
|
||||||
startLineNumber: 1,
|
getQuickInfoAtPosition(uri: string, offset: number): Promise<any>;
|
||||||
startColumn: 1,
|
getSignatureHelpItems(uri: string, offset: number): Promise<any>;
|
||||||
endLineNumber: position.lineNumber,
|
};
|
||||||
endColumn: position.column
|
|
||||||
});
|
|
||||||
|
|
||||||
const lastInterpolationStart = textUntilPosition.lastIndexOf('${');
|
async function getJavaScriptWorker(resource: monaco.Uri): Promise<JSWorker | null> {
|
||||||
if (lastInterpolationStart === -1) {
|
const ts = (monaco.languages as any).typescript;
|
||||||
return false;
|
if (!ts || !ts.getJavaScriptWorker) {
|
||||||
|
console.warn('monaco-json-interpolation: JavaScript language service not available');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const afterInterpolationStart = textUntilPosition.substring(lastInterpolationStart);
|
try {
|
||||||
return !afterInterpolationStart.includes('}');
|
const getWorker = await ts.getJavaScriptWorker();
|
||||||
|
const worker = await getWorker(resource);
|
||||||
|
return worker;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('monaco-json-interpolation: Failed to get JavaScript worker', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Interpolation context helpers ---
|
||||||
|
|
||||||
|
interface InterpolationContext {
|
||||||
|
/** The full expression inside ${...} */
|
||||||
|
expression: string;
|
||||||
|
/** Offset of ${ in the document */
|
||||||
|
interpolationStart: number;
|
||||||
|
/** Offset within the expression where cursor is */
|
||||||
|
offsetInExpression: number;
|
||||||
|
/** Start line/column of the interpolation */
|
||||||
|
startPosition: { lineNumber: number; column: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInterpolationContext(
|
||||||
|
model: monaco.editor.ITextModel,
|
||||||
|
position: monaco.Position
|
||||||
|
): InterpolationContext | null {
|
||||||
|
const text = model.getValue();
|
||||||
|
const offset = model.getOffsetAt(position);
|
||||||
|
|
||||||
|
// Find the ${...} that contains the current position
|
||||||
|
const beforeCursor = text.substring(0, offset);
|
||||||
|
const lastStart = beforeCursor.lastIndexOf('${');
|
||||||
|
|
||||||
|
if (lastStart === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's a closing } between ${ and cursor
|
||||||
|
const betweenStartAndCursor = text.substring(lastStart + 2, offset);
|
||||||
|
if (betweenStartAndCursor.includes('}')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the closing }
|
||||||
|
let depth = 1;
|
||||||
|
let endOffset = offset;
|
||||||
|
for (let i = offset; i < text.length; i++) {
|
||||||
|
if (text[i] === '{') depth++;
|
||||||
|
else if (text[i] === '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth === 0) {
|
||||||
|
endOffset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the expression (content between ${ and })
|
||||||
|
const expressionStart = lastStart + 2;
|
||||||
|
const expression = text.substring(expressionStart, endOffset);
|
||||||
|
const offsetInExpression = offset - expressionStart;
|
||||||
|
|
||||||
|
const startPos = model.getPositionAt(lastStart);
|
||||||
|
|
||||||
|
return {
|
||||||
|
expression,
|
||||||
|
interpolationStart: lastStart,
|
||||||
|
offsetInExpression,
|
||||||
|
startPosition: {
|
||||||
|
lineNumber: startPos.lineNumber,
|
||||||
|
column: startPos.column
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Virtual model for JavaScript worker ---
|
||||||
|
|
||||||
|
const VIRTUAL_JS_URI = monaco.Uri.parse('file:///interpolation-context.js');
|
||||||
|
let virtualJsModel: monaco.editor.ITextModel | null = null;
|
||||||
|
|
||||||
|
function getOrCreateVirtualJsModel(): monaco.editor.ITextModel {
|
||||||
|
if (!virtualJsModel || virtualJsModel.isDisposed()) {
|
||||||
|
virtualJsModel = monaco.editor.createModel('', 'javascript', VIRTUAL_JS_URI);
|
||||||
|
}
|
||||||
|
return virtualJsModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Providers ---
|
// --- Providers ---
|
||||||
|
|
||||||
function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
// Completion provider - combines JSON completions with variable completions
|
// Completion provider - combines JSON completions with JS completions for interpolation
|
||||||
monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, {
|
monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, {
|
||||||
triggerCharacters: ['"', ':', ' ', '$', '{'],
|
triggerCharacters: ['"', ':', ' ', '$', '{', '.'],
|
||||||
|
|
||||||
async provideCompletionItems(
|
async provideCompletionItems(
|
||||||
model: monaco.editor.ITextModel,
|
model: monaco.editor.ITextModel,
|
||||||
|
|
@ -203,11 +286,30 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
context: monaco.languages.CompletionContext
|
context: monaco.languages.CompletionContext
|
||||||
): Promise<monaco.languages.CompletionList | null> {
|
): Promise<monaco.languages.CompletionList | null> {
|
||||||
// Check if we're inside an interpolation
|
// Check if we're inside an interpolation
|
||||||
if (isInsideInterpolation(model, position)) {
|
const interpContext = getInterpolationContext(model, position);
|
||||||
// Provide variable completions
|
|
||||||
const variableContext = defaults.variableContext;
|
if (interpContext) {
|
||||||
if (variableContext) {
|
// Use JavaScript worker for completions inside interpolation
|
||||||
const variables = await variableContext.getVariables();
|
const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI);
|
||||||
|
if (!jsWorker) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update the virtual model with the expression
|
||||||
|
const virtualModel = getOrCreateVirtualJsModel();
|
||||||
|
virtualModel.setValue(interpContext.expression);
|
||||||
|
|
||||||
|
// Get completions from JavaScript worker
|
||||||
|
const info = await jsWorker.getCompletionsAtPosition(
|
||||||
|
VIRTUAL_JS_URI.toString(),
|
||||||
|
interpContext.offsetInExpression
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!info || !info.entries) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const wordInfo = model.getWordUntilPosition(position);
|
const wordInfo = model.getWordUntilPosition(position);
|
||||||
const range = {
|
const range = {
|
||||||
startLineNumber: position.lineNumber,
|
startLineNumber: position.lineNumber,
|
||||||
|
|
@ -216,18 +318,20 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
endColumn: wordInfo.endColumn
|
endColumn: wordInfo.endColumn
|
||||||
};
|
};
|
||||||
|
|
||||||
const suggestions: monaco.languages.CompletionItem[] = variables.map((variable) => ({
|
const suggestions: monaco.languages.CompletionItem[] = info.entries.map((entry: any) => ({
|
||||||
label: variable.name,
|
label: entry.name,
|
||||||
kind: monaco.languages.CompletionItemKind.Variable,
|
kind: convertTsCompletionKind(entry.kind),
|
||||||
detail: variable.detail || variable.type,
|
detail: entry.kindModifiers,
|
||||||
documentation: formatDocumentation(variable),
|
sortText: entry.sortText,
|
||||||
insertText: variable.name,
|
insertText: entry.insertText || entry.name,
|
||||||
range: range
|
range: range
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { suggestions };
|
return { suggestions };
|
||||||
|
} catch (e) {
|
||||||
|
console.error('JS completion error:', e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JSON completions from the worker
|
// Get JSON completions from the worker
|
||||||
|
|
@ -246,7 +350,6 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert LSP completions to Monaco completions
|
|
||||||
const suggestions: monaco.languages.CompletionItem[] = (info.items || []).map((item: any) => ({
|
const suggestions: monaco.languages.CompletionItem[] = (info.items || []).map((item: any) => ({
|
||||||
label: item.label,
|
label: item.label,
|
||||||
kind: convertCompletionItemKind(item.kind),
|
kind: convertCompletionItemKind(item.kind),
|
||||||
|
|
@ -269,57 +372,73 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hover provider - combines JSON hover with variable hover
|
// Hover provider - combines JSON hover with JS hover for interpolation
|
||||||
monaco.languages.registerHoverProvider(LANGUAGE_ID, {
|
monaco.languages.registerHoverProvider(LANGUAGE_ID, {
|
||||||
async provideHover(
|
async provideHover(
|
||||||
model: monaco.editor.ITextModel,
|
model: monaco.editor.ITextModel,
|
||||||
position: monaco.Position
|
position: monaco.Position
|
||||||
): Promise<monaco.languages.Hover | null> {
|
): Promise<monaco.languages.Hover | null> {
|
||||||
// Check if we're inside an interpolation
|
// Check if we're inside an interpolation
|
||||||
if (isInsideInterpolation(model, position)) {
|
const interpContext = getInterpolationContext(model, position);
|
||||||
const variableContext = defaults.variableContext;
|
|
||||||
if (variableContext) {
|
|
||||||
const wordInfo = model.getWordAtPosition(position);
|
|
||||||
if (wordInfo) {
|
|
||||||
const variables = await variableContext.getVariables();
|
|
||||||
const variable = variables.find((v) => v.name === wordInfo.word);
|
|
||||||
|
|
||||||
if (variable) {
|
if (interpContext) {
|
||||||
const contents: monaco.IMarkdownString[] = [];
|
// Use JavaScript worker for hover inside interpolation
|
||||||
|
const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI);
|
||||||
if (variable.type) {
|
if (!jsWorker) {
|
||||||
contents.push({
|
return null;
|
||||||
value: `\`\`\`typescript\n(variable) ${variable.name}: ${variable.type}\n\`\`\``
|
}
|
||||||
});
|
|
||||||
} else {
|
try {
|
||||||
contents.push({
|
// Update the virtual model with the expression
|
||||||
value: `\`\`\`typescript\n(variable) ${variable.name}\n\`\`\``
|
const virtualModel = getOrCreateVirtualJsModel();
|
||||||
});
|
virtualModel.setValue(interpContext.expression);
|
||||||
}
|
|
||||||
|
// Get quick info from JavaScript worker
|
||||||
if (variable.description) {
|
const info = await jsWorker.getQuickInfoAtPosition(
|
||||||
contents.push({ value: variable.description });
|
VIRTUAL_JS_URI.toString(),
|
||||||
}
|
interpContext.offsetInExpression
|
||||||
|
);
|
||||||
if (variable.value !== undefined) {
|
|
||||||
contents.push({
|
if (!info) {
|
||||||
value: `**Current value:**\n\`\`\`json\n${JSON.stringify(variable.value, null, 2)}\n\`\`\``
|
return null;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
const contents: monaco.IMarkdownString[] = [];
|
||||||
return {
|
|
||||||
contents,
|
// Display string (type signature)
|
||||||
range: {
|
if (info.displayParts) {
|
||||||
startLineNumber: position.lineNumber,
|
const displayText = info.displayParts.map((p: any) => p.text).join('');
|
||||||
startColumn: wordInfo.startColumn,
|
contents.push({
|
||||||
endLineNumber: position.lineNumber,
|
value: '```typescript\n' + displayText + '\n```'
|
||||||
endColumn: wordInfo.endColumn
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
// Documentation
|
||||||
}
|
if (info.documentation && info.documentation.length > 0) {
|
||||||
|
const docText = info.documentation.map((p: any) => p.text).join('');
|
||||||
|
contents.push({ value: docText });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate range in the original document
|
||||||
|
const wordInfo = model.getWordAtPosition(position);
|
||||||
|
const range = wordInfo
|
||||||
|
? {
|
||||||
|
startLineNumber: position.lineNumber,
|
||||||
|
startColumn: wordInfo.startColumn,
|
||||||
|
endLineNumber: position.lineNumber,
|
||||||
|
endColumn: wordInfo.endColumn
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return { contents, range };
|
||||||
|
} catch (e) {
|
||||||
|
console.error('JS hover error:', e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JSON hover from the worker
|
// Get JSON hover from the worker
|
||||||
|
|
@ -356,6 +475,69 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Signature help provider for function calls inside interpolation
|
||||||
|
monaco.languages.registerSignatureHelpProvider(LANGUAGE_ID, {
|
||||||
|
signatureHelpTriggerCharacters: ['(', ','],
|
||||||
|
|
||||||
|
async provideSignatureHelp(
|
||||||
|
model: monaco.editor.ITextModel,
|
||||||
|
position: monaco.Position
|
||||||
|
): Promise<monaco.languages.SignatureHelpResult | null> {
|
||||||
|
const interpContext = getInterpolationContext(model, position);
|
||||||
|
if (!interpContext) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI);
|
||||||
|
if (!jsWorker) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const virtualModel = getOrCreateVirtualJsModel();
|
||||||
|
virtualModel.setValue(interpContext.expression);
|
||||||
|
|
||||||
|
const info = await jsWorker.getSignatureHelpItems(
|
||||||
|
VIRTUAL_JS_URI.toString(),
|
||||||
|
interpContext.offsetInExpression
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!info || !info.items || info.items.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signatures: monaco.languages.SignatureInformation[] = info.items.map((item: any) => {
|
||||||
|
const params: monaco.languages.ParameterInformation[] = item.parameters.map((p: any) => ({
|
||||||
|
label: p.displayParts.map((d: any) => d.text).join(''),
|
||||||
|
documentation: p.documentation?.map((d: any) => d.text).join('') || undefined
|
||||||
|
}));
|
||||||
|
|
||||||
|
const prefix = item.prefixDisplayParts?.map((p: any) => p.text).join('') || '';
|
||||||
|
const suffix = item.suffixDisplayParts?.map((p: any) => p.text).join('') || '';
|
||||||
|
const separator = item.separatorDisplayParts?.map((p: any) => p.text).join('') || ', ';
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: prefix + params.map((p) => p.label).join(separator) + suffix,
|
||||||
|
documentation: item.documentation?.map((d: any) => d.text).join('') || undefined,
|
||||||
|
parameters: params
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
signatures,
|
||||||
|
activeSignature: info.selectedItemIndex || 0,
|
||||||
|
activeParameter: info.argumentIndex || 0
|
||||||
|
},
|
||||||
|
dispose: () => {}
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error('JS signature help error:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Diagnostics adapter
|
// Diagnostics adapter
|
||||||
setupDiagnostics(defaults);
|
setupDiagnostics(defaults);
|
||||||
|
|
||||||
|
|
@ -721,6 +903,35 @@ function convertCompletionItemKind(kind: number): monaco.languages.CompletionIte
|
||||||
return kindMap[kind] || monaco.languages.CompletionItemKind.Property;
|
return kindMap[kind] || monaco.languages.CompletionItemKind.Property;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertTsCompletionKind(kind: string): monaco.languages.CompletionItemKind {
|
||||||
|
const kindMap: { [key: string]: monaco.languages.CompletionItemKind } = {
|
||||||
|
primitive: monaco.languages.CompletionItemKind.Keyword,
|
||||||
|
keyword: monaco.languages.CompletionItemKind.Keyword,
|
||||||
|
var: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
let: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
const: monaco.languages.CompletionItemKind.Constant,
|
||||||
|
localVar: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
function: monaco.languages.CompletionItemKind.Function,
|
||||||
|
localFunction: monaco.languages.CompletionItemKind.Function,
|
||||||
|
method: monaco.languages.CompletionItemKind.Method,
|
||||||
|
getter: monaco.languages.CompletionItemKind.Property,
|
||||||
|
setter: monaco.languages.CompletionItemKind.Property,
|
||||||
|
property: monaco.languages.CompletionItemKind.Property,
|
||||||
|
constructor: monaco.languages.CompletionItemKind.Constructor,
|
||||||
|
class: monaco.languages.CompletionItemKind.Class,
|
||||||
|
interface: monaco.languages.CompletionItemKind.Interface,
|
||||||
|
type: monaco.languages.CompletionItemKind.Interface,
|
||||||
|
enum: monaco.languages.CompletionItemKind.Enum,
|
||||||
|
enumMember: monaco.languages.CompletionItemKind.EnumMember,
|
||||||
|
module: monaco.languages.CompletionItemKind.Module,
|
||||||
|
alias: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
typeParameter: monaco.languages.CompletionItemKind.TypeParameter,
|
||||||
|
parameter: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
string: monaco.languages.CompletionItemKind.Value
|
||||||
|
};
|
||||||
|
return kindMap[kind] || monaco.languages.CompletionItemKind.Property;
|
||||||
|
}
|
||||||
|
|
||||||
function convertSymbolKind(kind: number): monaco.languages.SymbolKind {
|
function convertSymbolKind(kind: number): monaco.languages.SymbolKind {
|
||||||
const kindMap: { [key: number]: monaco.languages.SymbolKind } = {
|
const kindMap: { [key: number]: monaco.languages.SymbolKind } = {
|
||||||
1: monaco.languages.SymbolKind.File,
|
1: monaco.languages.SymbolKind.File,
|
||||||
|
|
@ -765,25 +976,6 @@ function convertSymbol(symbol: any): monaco.languages.DocumentSymbol {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDocumentation(
|
|
||||||
variable: VariableDefinition
|
|
||||||
): string | monaco.IMarkdownString {
|
|
||||||
let doc = '';
|
|
||||||
|
|
||||||
if (variable.description) {
|
|
||||||
doc += variable.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variable.value !== undefined) {
|
|
||||||
if (doc) {
|
|
||||||
doc += '\n\n';
|
|
||||||
}
|
|
||||||
doc += `**Current value:** \`${JSON.stringify(variable.value)}\``;
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc ? { value: doc } : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Convenience export ---
|
// --- Convenience export ---
|
||||||
|
|
||||||
export const jsonInterpolation = {
|
export const jsonInterpolation = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue