diff --git a/README.md b/README.md index 47013fd..e671eeb 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,10 @@ To set a template file, either set the `@@x-template` element in your `arb` file ``` or set a file to be the template in the `l10n.yaml` using: ```yaml -template-arb-file: path/to/template.arb +arb-dir: path/to +template-arb-file: template.arb ``` + You can suppress specific diagnostics warnings by adding into `settings.json`: ```json "arb-editor.suppressedWarnings": string | string[] diff --git a/src/extension.ts b/src/extension.ts index b556c43..93224b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -27,9 +27,6 @@ import { CodeActions } from './codeactions'; import { Decorator } from './decorate'; import { Diagnostics } from './diagnose'; import { Literal, MessageList, Parser } from './messageParser'; -import { locateL10nYaml } from './project'; -import YAML = require('yaml'); -import fs = require('fs'); const snippetsJson = require("../snippets/snippets.json"); const snippetsInlineJson = require("../snippets/snippets_inline.json"); @@ -87,10 +84,14 @@ export async function activate(context: vscode.ExtensionContext) { if (!editor || isNotArbFile(editor.document)) { return; } - var l10nYamlPath = locateL10nYaml(editor.document.uri.fsPath); - var l10nOptions: L10nYaml | undefined; - if (l10nYamlPath) { - l10nOptions = parseYaml(l10nYamlPath); + + function parseAndDecorate(): MessageList { + return parser.parseAndDecorate({ + editor: editor!, + decorator: decorator, + diagnostics: diagnostics, + quickfixes: quickfixes, + }).messageList; } if (executeDelayed && pendingDecorations) { @@ -103,38 +104,6 @@ export async function activate(context: vscode.ExtensionContext) { commonMessageList = parseAndDecorate(); } } - - function parseAndDecorate(): MessageList { - let templatePathFromOptions = l10nOptions?.['template-arb-file']; - let templateMessageList: MessageList | undefined; - let templateErrors: Literal[] | undefined; - let [templatePathFromFile, messageList, errors] = parser.parse(editor!.document.getText(), l10nOptions)!; - if (templatePathFromOptions || templatePathFromFile) { - let templatePath: string; - if (templatePathFromFile) { - if (path.isAbsolute(templatePathFromFile)) { - templatePath = templatePathFromFile; - } else { - templatePath = path.join(path.dirname(editor?.document.uri.path!), templatePathFromFile); - } - } else { - if (path.isAbsolute(templatePathFromOptions!)) { - templatePath = templatePathFromOptions!; - } else { - templatePath = path.join(path.dirname(l10nYamlPath!), templatePathFromOptions!); - } - } - if (templatePath !== editor!.document.uri.fsPath) { - const template = fs.readFileSync(templatePath, "utf8"); - // TODO(mosuem): Allow chaining of template files. - [, templateMessageList, templateErrors] = parser.parse(template, l10nOptions)!; - } - } - decorator.decorate(editor!, messageList); - diagnostics.diagnose(editor!, messageList, errors, templateMessageList); - quickfixes.update(messageList); - return messageList; - } } } @@ -164,14 +133,6 @@ function getSnippets(snippetsJson: any): vscode.CompletionList { // This method is called when your extension is deactivated export function deactivate() { } -function parseYaml(uri: string): L10nYaml | undefined { - if (!fs.existsSync(uri)) { - return; - } - const yaml = fs.readFileSync(uri, "utf8"); - return YAML.parse(yaml) as L10nYaml; -} - export interface L10nYaml { 'arb-dir'?: string | undefined; 'output-dir'?: string | undefined; diff --git a/src/messageParser.ts b/src/messageParser.ts index 645318f..acbc852 100644 --- a/src/messageParser.ts +++ b/src/messageParser.ts @@ -10,13 +10,21 @@ // limitations under the License. 'use strict'; +import * as vscode from 'vscode'; import { JSONPath, visit } from 'jsonc-parser'; import XRegExp = require('xregexp'); +import { locateL10nYaml } from './project'; import { L10nYaml } from './extension'; +import { Diagnostics } from './diagnose'; +import { Decorator } from './decorate'; +import { CodeActions } from './codeactions'; +import path = require('path'); +import YAML = require('yaml'); +import fs = require('fs'); export class Parser { - parse(document: string, l10nOptions?: L10nYaml): [string|undefined, MessageList, Literal[]] { + parse(document: string, l10nOptions?: L10nYaml): [MessageList, Literal[]] { let templatePath: string | undefined; const messages: MessageEntry[] = []; const metadata: MessageEntry[] = []; @@ -25,7 +33,7 @@ export class Parser { let inTemplateTag = false; let placeholderLevel: number | null = null; let metadataLevel: number | null = null; - let metadataKey: Key | null = null; + let metadataKey: Key | null = null; let messageKey: Key | null = null; let definedPlaceholders: PlaceholderMetadata[] = []; let errors: Literal[] = []; @@ -172,8 +180,84 @@ export class Parser { } } - return [templatePath, new MessageList(templatePath, indentation ?? 0, indentationCharacter ?? ' ', messages, metadata), errors]; + return [new MessageList(templatePath, indentation ?? 0, indentationCharacter ?? ' ', messages, metadata), errors]; } + + + private resolveTemplatePath({ + document, + messageList, + l10nYamlPath, + l10nOptions, + }: { + document: vscode.TextDocument; + messageList: MessageList; + } & L10nYamlPathAndOptions): string | undefined { + if (messageList.templatePath) { + return path.isAbsolute(messageList.templatePath) + ? messageList.templatePath + : path.join(path.dirname(document.uri.fsPath), messageList.templatePath); + } else if (l10nOptions !== undefined) { + const templateRootFromOptions = l10nOptions?.['arb-dir'] ?? 'lib/l10n'; + const templatePathFromOptions = l10nOptions?.['template-arb-file'] ?? 'app_en.arb'; + + return path.isAbsolute(templatePathFromOptions) + ? templatePathFromOptions + : path.join(path.dirname(l10nYamlPath), templateRootFromOptions, templatePathFromOptions); + } + } + + parseAndDecorate({ + editor, + decorator, + diagnostics, + quickfixes, + }: ParseAndDecorateOptions): ParseAndDecorateResult { + let templateMessageList: MessageList | undefined; + + const l10nYamlPath = locateL10nYaml(editor.document.uri.fsPath); + const l10nOptions = l10nYamlPath + ? parseYaml(l10nYamlPath) + : undefined; + const [messageList, errors] = this.parse(editor.document.getText(), l10nOptions)!; + + const templatePath = this.resolveTemplatePath({ + document: editor.document, + messageList, + l10nYamlPath: l10nYamlPath!, + l10nOptions: l10nOptions, + }); + + if (templatePath && templatePath !== editor.document.uri.fsPath) { + const template = fs.readFileSync(templatePath, "utf8"); + // TODO(mosuem): Allow chaining of template files. + [templateMessageList,] = this.parse(template, l10nOptions)!; + } + + const decorations = decorator.decorate(editor, messageList); + const diags = diagnostics.diagnose(editor, messageList, errors, templateMessageList); + quickfixes.update(messageList); + return { messageList, decorations, diagnostics: diags }; + } +} + +type L10nYamlPathAndOptions = { + l10nYamlPath: string; + l10nOptions: L10nYaml; +} | { + l10nYamlPath: string | undefined; + l10nOptions: undefined; +}; +interface ParseAndDecorateResult { + messageList: MessageList; + decorations: Map; + diagnostics: vscode.Diagnostic[]; +} +interface ParseAndDecorateOptions { + editor: vscode.TextEditor; + decorator: Decorator; + diagnostics: Diagnostics; + quickfixes: CodeActions; } function matchCurlyBrackets(v: string, l10nOptions?: L10nYaml): XRegExp.MatchRecursiveValueNameMatch[] { @@ -189,6 +273,14 @@ function matchCurlyBrackets(v: string, l10nOptions?: L10nYaml): XRegExp.MatchRec return values; } +function parseYaml(uri: string): L10nYaml | undefined { + if (!fs.existsSync(uri)) { + return; + } + const yaml = fs.readFileSync(uri, "utf8"); + return YAML.parse(yaml) as L10nYaml; +} + export function getUnescapedRegions(expression: string, l10nOptions?: L10nYaml): [number, number][] { const unEscapedRegions: [number, number][] = []; diff --git a/src/test/l10nYaml/arb-dir/_l10n/app_en.arb b/src/test/l10nYaml/arb-dir/_l10n/app_en.arb new file mode 100644 index 0000000..2d6ff2d --- /dev/null +++ b/src/test/l10nYaml/arb-dir/_l10n/app_en.arb @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "appName": "Demo app", + "dir": "dir" +} \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir/_l10n/testarb_2.annotated b/src/test/l10nYaml/arb-dir/_l10n/testarb_2.annotated new file mode 100644 index 0000000..c03563f --- /dev/null +++ b/src/test/l10nYaml/arb-dir/_l10n/testarb_2.annotated @@ -0,0 +1,4 @@ +{ + "@@locale": "en" +} + [Warning]:"Missing messages from template: appName, dir" \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir/_l10n/testarb_2.arb b/src/test/l10nYaml/arb-dir/_l10n/testarb_2.arb new file mode 100644 index 0000000..9dae4a2 --- /dev/null +++ b/src/test/l10nYaml/arb-dir/_l10n/testarb_2.arb @@ -0,0 +1,3 @@ +{ + "@@locale": "en" +} \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir/l10n.yaml b/src/test/l10nYaml/arb-dir/l10n.yaml new file mode 100644 index 0000000..1b6e6c2 --- /dev/null +++ b/src/test/l10nYaml/arb-dir/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: _l10n +output-class: Words +output-localization-file: app_localizations.dart +untranslated-messages-file: lib/_l10n/desiredFileName.txt diff --git a/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb.arb b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb.arb new file mode 100644 index 0000000..5351fd9 --- /dev/null +++ b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb.arb @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "appName": "Demo app", + "dirAndTemplate": "dirAndTemplate" +} \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.annotated b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.annotated new file mode 100644 index 0000000..266559f --- /dev/null +++ b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.annotated @@ -0,0 +1,4 @@ +{ + "@@locale": "en" +} + [Warning]:"Missing messages from template: appName, dirAndTemplate" \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.arb b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.arb new file mode 100644 index 0000000..9dae4a2 --- /dev/null +++ b/src/test/l10nYaml/arb-dir_template-arb-file/_l10n/testarb_2.arb @@ -0,0 +1,3 @@ +{ + "@@locale": "en" +} \ No newline at end of file diff --git a/src/test/l10nYaml/arb-dir_template-arb-file/l10n.yaml b/src/test/l10nYaml/arb-dir_template-arb-file/l10n.yaml new file mode 100644 index 0000000..8109eb0 --- /dev/null +++ b/src/test/l10nYaml/arb-dir_template-arb-file/l10n.yaml @@ -0,0 +1,5 @@ +arb-dir: _l10n +template-arb-file: testarb.arb +output-class: Words +output-localization-file: app_localizations.dart +untranslated-messages-file: lib/_l10n/desiredFileName.txt diff --git a/src/test/l10nYaml/empty/l10n.yaml b/src/test/l10nYaml/empty/l10n.yaml new file mode 100644 index 0000000..e69de29 diff --git a/src/test/l10nYaml/empty/lib/l10n/app_en.arb b/src/test/l10nYaml/empty/lib/l10n/app_en.arb new file mode 100644 index 0000000..ce61832 --- /dev/null +++ b/src/test/l10nYaml/empty/lib/l10n/app_en.arb @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "appName": "Demo app", + "empty": "empty" +} \ No newline at end of file diff --git a/src/test/l10nYaml/empty/lib/l10n/testarb_2.annotated b/src/test/l10nYaml/empty/lib/l10n/testarb_2.annotated new file mode 100644 index 0000000..84ea63e --- /dev/null +++ b/src/test/l10nYaml/empty/lib/l10n/testarb_2.annotated @@ -0,0 +1,4 @@ +{ + "@@locale": "en" +} + [Warning]:"Missing messages from template: appName, empty" \ No newline at end of file diff --git a/src/test/l10nYaml/empty/lib/l10n/testarb_2.arb b/src/test/l10nYaml/empty/lib/l10n/testarb_2.arb new file mode 100644 index 0000000..9dae4a2 --- /dev/null +++ b/src/test/l10nYaml/empty/lib/l10n/testarb_2.arb @@ -0,0 +1,3 @@ +{ + "@@locale": "en" +} \ No newline at end of file diff --git a/src/test/l10nYaml/template-arb-file/l10n.yaml b/src/test/l10nYaml/template-arb-file/l10n.yaml new file mode 100644 index 0000000..54e61b1 --- /dev/null +++ b/src/test/l10nYaml/template-arb-file/l10n.yaml @@ -0,0 +1,4 @@ +template-arb-file: testarb.arb +output-class: Words +output-localization-file: app_localizations.dart +untranslated-messages-file: lib/_l10n/desiredFileName.txt diff --git a/src/test/l10nYaml/template-arb-file/lib/l10n/testarb.arb b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb.arb new file mode 100644 index 0000000..e4d076a --- /dev/null +++ b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb.arb @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "appName": "Demo app", + "template": "template" +} \ No newline at end of file diff --git a/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.annotated b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.annotated new file mode 100644 index 0000000..412e42f --- /dev/null +++ b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.annotated @@ -0,0 +1,4 @@ +{ + "@@locale": "en" +} + [Warning]:"Missing messages from template: appName, template" \ No newline at end of file diff --git a/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.arb b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.arb new file mode 100644 index 0000000..9dae4a2 --- /dev/null +++ b/src/test/l10nYaml/template-arb-file/lib/l10n/testarb_2.arb @@ -0,0 +1,3 @@ +{ + "@@locale": "en" +} \ No newline at end of file diff --git a/src/test/l10nYaml/with_x-template/l10n.yaml b/src/test/l10nYaml/with_x-template/l10n.yaml new file mode 100644 index 0000000..a69243c --- /dev/null +++ b/src/test/l10nYaml/with_x-template/l10n.yaml @@ -0,0 +1,5 @@ +arb-dir: lib/_l10n +template-arb-file: app_ja.arb +output-class: Words +output-localization-file: app_localizations.dart +untranslated-messages-file: lib/_l10n/desiredFileName.txt diff --git a/src/test/l10nYaml/with_x-template/l10n/testarb.arb b/src/test/l10nYaml/with_x-template/l10n/testarb.arb new file mode 100644 index 0000000..cb0f9df --- /dev/null +++ b/src/test/l10nYaml/with_x-template/l10n/testarb.arb @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "appName": "Demo app", + "withTemplate": "withTemplate" +} \ No newline at end of file diff --git a/src/test/l10nYaml/with_x-template/l10n/testarb_2.annotated b/src/test/l10nYaml/with_x-template/l10n/testarb_2.annotated new file mode 100644 index 0000000..3485ed0 --- /dev/null +++ b/src/test/l10nYaml/with_x-template/l10n/testarb_2.annotated @@ -0,0 +1,5 @@ +{ + "@@locale": "en", + "@@x-template": "testarb.arb" +} + [Warning]:"Missing messages from template: appName, withTemplate" \ No newline at end of file diff --git a/src/test/l10nYaml/with_x-template/l10n/testarb_2.arb b/src/test/l10nYaml/with_x-template/l10n/testarb_2.arb new file mode 100644 index 0000000..6212353 --- /dev/null +++ b/src/test/l10nYaml/with_x-template/l10n/testarb_2.arb @@ -0,0 +1,4 @@ +{ + "@@locale": "en", + "@@x-template": "testarb.arb" +} \ No newline at end of file diff --git a/src/test/pubspec.yaml b/src/test/pubspec.yaml new file mode 100644 index 0000000..1a44341 --- /dev/null +++ b/src/test/pubspec.yaml @@ -0,0 +1 @@ +name: Dummy \ No newline at end of file diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 7eeccc3..02fd78d 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -20,6 +20,8 @@ import * as vscode from 'vscode'; import { placeholderDecoration, selectDecoration, pluralDecoration, Decorator } from '../../decorate'; import { CombinedMessage, Key, Literal, MessageList, Parser, getUnescapedRegions } from '../../messageParser'; import { DiagnosticCode, Diagnostics } from '../../diagnose'; +import { L10nYaml } from '../../extension'; +import { CodeActions } from '../../codeactions'; const annotationNames = new Map([ [placeholderDecoration, '[decoration]placeholder'], @@ -30,19 +32,19 @@ const annotationNames = new Map([ suite('Extension Test Suite', async () => { test("Decorate golden file.", async () => { await updateConfiguration(null); - const contentWithAnnotations = await buildContentWithAnnotations('testarb.arb', undefined); + const contentWithAnnotations = await buildContentWithAnnotations('testarb.arb'); await compareGolden(contentWithAnnotations, 'testarb.annotated'); }); test("Decorate golden file with template.", async () => { await updateConfiguration(null); - const contentWithAnnotations = await buildContentWithAnnotations('testarb_2.arb', 'testarb.arb'); + const contentWithAnnotations = await buildContentWithAnnotations('testarb_2.arb'); await compareGolden(contentWithAnnotations, 'testarb_2.annotated'); }); test("Decorate golden file with template that has no missing messages.", async () => { await updateConfiguration(null); - const contentWithAnnotations = await buildContentWithAnnotations('testarb_3.arb', 'testarb.arb'); + const contentWithAnnotations = await buildContentWithAnnotations('testarb_3.arb'); await compareGolden(contentWithAnnotations, 'testarb_3.annotated'); }); @@ -78,7 +80,7 @@ suite('Extension Test Suite', async () => { } } }`; - const [, messages, errors] = new Parser().parse(document); + const [messages, errors] = new Parser().parse(document); assert.equal(errors.length, 0); assert.equal(messages.messageEntries.length, 6); assert.equal(messages.metadataEntries.length, 5); @@ -114,7 +116,7 @@ suite('Extension Test Suite', async () => { const document = await getEditor('testarb.arb'); - const [, messageList, errors] = new Parser().parse(document.document.getText())!; + const [messageList, errors] = new Parser().parse(document.document.getText())!; const diagnosticsList = new Diagnostics().diagnose(document, messageList, errors, undefined); assert.equal(diagnosticsList.length, 0); @@ -123,10 +125,10 @@ suite('Extension Test Suite', async () => { test("Test suppressed warning with id missing_metadata_for_key", async () => { const id = DiagnosticCode.missingMetadataForKey; await updateConfiguration([id]); - + const document = await getEditor('quickfix.arb'); - const [, messageList, errors] = new Parser().parse(document.document.getText())!; + const [messageList, errors] = new Parser().parse(document.document.getText())!; const diagnosticsList = new Diagnostics().diagnose(document, messageList, errors, undefined); assert.equal(diagnosticsList.every(item => item.code !== id), true); @@ -135,10 +137,10 @@ suite('Extension Test Suite', async () => { test("Test suppressed warning with id 'invalid_key', 'missing_metadata_for_key'", async () => { const ids = [DiagnosticCode.invalidKey, DiagnosticCode.missingMetadataForKey]; await updateConfiguration(ids); - + const document = await getEditor('testarb_2.arb'); - const [, messageList, errors] = new Parser().parse(document.document.getText())!; + const [messageList, errors] = new Parser().parse(document.document.getText())!; const diagnosticsList = new Diagnostics().diagnose(document, messageList, errors, undefined); assert.equal(diagnosticsList.every(item => !ids.includes(item.code as DiagnosticCode)), true); @@ -147,14 +149,53 @@ suite('Extension Test Suite', async () => { test("Test suppressed warning with code 'metadata_for_missing_key'", async () => { const id = DiagnosticCode.metadataForMissingKey; await updateConfiguration([id]); - + const document = await getEditor('testarb_2.arb'); - const [, messageList, errors] = new Parser().parse(document.document.getText())!; + const [messageList, errors] = new Parser().parse(document.document.getText())!; const diagnosticsList = new Diagnostics().diagnose(document, messageList, errors, undefined); assert.equal(diagnosticsList.every(item => item.code !== id), true); }); + + suite('Template Path', async () => { + /* eslint-disable @typescript-eslint/naming-convention */ + test("Resolve template path from @@x-template with L10nYaml", async () => { + const testDir = 'l10nYaml/with_x-template/l10n'; + await updateConfiguration(null); + const contentWithAnnotations = await buildContentWithAnnotations(`${testDir}/testarb_2.arb`); + await compareGolden(contentWithAnnotations, `${testDir}/testarb_2.annotated`); + }); + + test("Resolve template path from empty L10nYaml", async () => { + const testDir = 'l10nYaml/empty/lib/l10n'; + await updateConfiguration(null); + const contentWithAnnotations = await buildContentWithAnnotations(`${testDir}/testarb_2.arb`); + await compareGolden(contentWithAnnotations, `${testDir}/testarb_2.annotated`); + }); + + test("Resolve template path from L10nYaml with arb-dir and template-arb-file", async () => { + const testDir = 'l10nYaml/arb-dir_template-arb-file/_l10n'; + await updateConfiguration(null); + const contentWithAnnotations = await buildContentWithAnnotations(`${testDir}/testarb_2.arb`); + await compareGolden(contentWithAnnotations, `${testDir}/testarb_2.annotated`); + }); + + test("Resolve template path from L10nYaml with template-arb-file", async () => { + const testDir = 'l10nYaml/template-arb-file/lib/l10n'; + await updateConfiguration(null); + const contentWithAnnotations = await buildContentWithAnnotations(`${testDir}/testarb_2.arb`); + await compareGolden(contentWithAnnotations, `${testDir}/testarb_2.annotated`); + }); + + test("Resolve template path from L10nYaml with arb-dir", async () => { + const testDir = 'l10nYaml/arb-dir/_l10n'; + await updateConfiguration(null); + const contentWithAnnotations = await buildContentWithAnnotations(`${testDir}/testarb_2.arb`); + await compareGolden(contentWithAnnotations, `${testDir}/testarb_2.annotated`); + }); + /* eslint-enable */ + }); }); const testFolderLocation: string = "/../../../src/test/"; @@ -173,7 +214,7 @@ async function testFixAgainstGolden(testFile: string, getItemFromParsed: (messag const editor = await getEditor(testFile); // Parse original - const [, messageList,] = new Parser().parse(editor.document.getText()); + const [messageList,] = new Parser().parse(editor.document.getText()); // Apply fix for placeholder not defined in metadata const item = getItemFromParsed(messageList); @@ -209,18 +250,18 @@ async function regenerateGolden(newContent: string, goldenFilename: string) { await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(newContent)); } -async function buildContentWithAnnotations(filename: string, templateFile: string | undefined) { +async function buildContentWithAnnotations(filename: string) { const editor = await getEditor(filename); const editorEol = editor.document.eol === vscode.EndOfLine.CRLF ? "\r\n" : "\n"; - const [, messageList, errors] = new Parser().parse(editor.document.getText())!; - let templateMessageList: MessageList | undefined; - let templateErrors: Literal[] | undefined; - if (templateFile) { - const templateEditor = await getEditor(templateFile); - [, templateMessageList, templateErrors] = new Parser().parse(templateEditor.document.getText())!; - } - const decorations = new Decorator().decorate(editor, messageList); - const diagnostics = new Diagnostics().diagnose(editor, messageList, errors, templateMessageList); + + const { decorations, diagnostics } = new Parser().parseAndDecorate({ + editor, + decorator: new Decorator(), + diagnostics: new Diagnostics(), + quickfixes: new CodeActions(), + }); + + const content = editor.document.getText(); const annotationsForLine = new Map(); for (const entry of decorations.entries() ?? []) {