Skip to content

Commit

Permalink
Add golden test (#8)
Browse files Browse the repository at this point in the history
* Add golden test

* Small changes as per review
  • Loading branch information
mosuem authored Jan 26, 2023
1 parent f3b4153 commit 71ce71e
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 21 deletions.
1 change: 0 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export async function activate(context: vscode.ExtensionContext) {
decoratorAndParser.parseAndDecorate(activeTextEditor);
}


// At extension startup
}

Expand Down
28 changes: 14 additions & 14 deletions src/parseAndDecorate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ import { JSONPath, visit } from 'jsonc-parser';
import * as vscode from 'vscode';
import XRegExp = require('xregexp');

const argDecoration = vscode.window.createTextEditorDecorationType({
export const argDecoration = vscode.window.createTextEditorDecorationType({
light: {
color: '#ff6f00'
},
dark: {
color: '#fff9c4'
}
});
const selectDecoration = vscode.window.createTextEditorDecorationType({
export const selectDecoration = vscode.window.createTextEditorDecorationType({
light: {
color: '#6a1b9a'
},
dark: {
color: '#ce93d8'
}
});
const pluralDecoration = vscode.window.createTextEditorDecorationType({
export const pluralDecoration = vscode.window.createTextEditorDecorationType({
light: {
color: '#0277bd'
},
Expand Down Expand Up @@ -58,11 +58,11 @@ class Literal {
export class DecoratorAndParser {
diagnostics = vscode.languages.createDiagnosticCollection("arb");

constructor(context: vscode.ExtensionContext) {
context.subscriptions.push(this.diagnostics);
constructor(context?: vscode.ExtensionContext) {
context?.subscriptions.push(this.diagnostics);
}

parseAndDecorate(editor: vscode.TextEditor) {
parseAndDecorate(editor: vscode.TextEditor): { diagnostics: vscode.Diagnostic[]; decorations: Map<vscode.TextEditorDecorationType, vscode.Range[]>; } | null {
// Prefill decorations map to avoid having old decoration hanging around
let decorationsMap = new Map<vscode.TextEditorDecorationType, vscode.Range[]>([
[argDecoration, []],
Expand All @@ -76,7 +76,7 @@ export class DecoratorAndParser {

// Only trigger on arb files
if (!editor || !path.basename(editor.document.fileName).endsWith('.arb')) {
return;
return null;
}
let nestingLevel = 0;
let placeholderLevel: number | null;
Expand All @@ -103,7 +103,7 @@ export class DecoratorAndParser {
onObjectProperty: (property: string, offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => {
if (placeholderLevel === nestingLevel - 1) {
if (!placeHoldersForKey.get(messageKey!)!.some((literal: Literal, index: number, array: Literal[]) => literal.value === property)) {
showErrorAt(offset + 1, offset + property.length + 1, `Placeholder ${property} is being declared, but not used in message.`, vscode.DiagnosticSeverity.Warning);
showErrorAt(offset + 1, offset + property.length + 1, `Placeholder "${property}" is being declared, but not used in message.`, vscode.DiagnosticSeverity.Warning);
}
definedPlaceholders.push(property);
decorateAt(offset + 1, offset + property.length + 1, argDecoration);
Expand All @@ -116,15 +116,15 @@ export class DecoratorAndParser {
const isGlobalMetadata = property.startsWith('@@');
const messageKeyExists = placeHoldersForKey.has(property.substring(1));
if (!isGlobalMetadata && !messageKeyExists) {
showErrorAt(propertyOffsetStart, propertyOffsetEnd, `Metadata for an undefined key. Add a message key with the name ${property.substring(1)}.`, vscode.DiagnosticSeverity.Error);
showErrorAt(propertyOffsetStart, propertyOffsetEnd, `Metadata for an undefined key. Add a message key with the name "${property.substring(1)}".`, vscode.DiagnosticSeverity.Error);
}
metadataLevel = nestingLevel;
} else {
if (keyNameRegex.exec(property) !== null) {
messageKey = property;
placeHoldersForKey.set(messageKey, []);
} else {
showErrorAt(propertyOffsetStart, propertyOffsetEnd, `${property} is not a valid message key.`, vscode.DiagnosticSeverity.Error);
showErrorAt(propertyOffsetStart, propertyOffsetEnd, `Key "${property}" is not a valid message key.`, vscode.DiagnosticSeverity.Error);
}
}
}
Expand All @@ -138,7 +138,7 @@ export class DecoratorAndParser {
placeholderLevel = null;
for (const placeholder of placeHoldersForKey.get(messageKey!)!) {
if (!definedPlaceholders.includes(placeholder.value)) {
showErrorAt(placeholder.start, placeholder.end, `Placeholder ${placeholder.value} not defined in the message metadata.`, vscode.DiagnosticSeverity.Warning);
showErrorAt(placeholder.start, placeholder.end, `Placeholder "${placeholder.value}" not defined in the message metadata.`, vscode.DiagnosticSeverity.Warning);
}
}
definedPlaceholders = [];
Expand Down Expand Up @@ -171,7 +171,7 @@ export class DecoratorAndParser {
placeHoldersForKey.get(messageKey!)!.push(new Literal(part, partOffset, partOffsetEnd));
decorateAt(partOffset, partOffsetEnd, argDecoration);
} else {
showErrorAt(partOffset, partOffsetEnd, 'This is not a valid argument name.', vscode.DiagnosticSeverity.Error);
showErrorAt(partOffset, partOffsetEnd, `"${part}" is not a valid argument name.`, vscode.DiagnosticSeverity.Error);
}
} else {
decorateMessage(part, partOffset - 1, colorMap, editor, true);
Expand Down Expand Up @@ -211,9 +211,9 @@ export class DecoratorAndParser {
const range = new vscode.Range(editor.document.positionAt(start), editor.document.positionAt(end));
diagnosticsList.push(new vscode.Diagnostic(range, errorMessage, severity));
}
}


return { diagnostics: diagnosticsList, decorations: decorationsMap };
}
}
function matchCurlyBrackets(value: string) {
return XRegExp.matchRecursive(value, '\\{', '\\}', 'g', {
Expand Down
74 changes: 68 additions & 6 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,79 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import * as assert from 'assert';
import path = require('path');
import { TextEncoder } from 'util';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
import { argDecoration, selectDecoration, pluralDecoration, DecoratorAndParser } from '../../parseAndDecorate';

suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
const annotationNames = new Map<vscode.TextEditorDecorationType, string>([
[argDecoration, '[decoration]argument'],
[selectDecoration, '[decoration]select'],
[pluralDecoration, '[decoration]plural'],
]);

test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
suite('Extension Test Suite', async () => {
test("should annotate function with parameters", async () => {
const contentWithAnnotations = await buildContentWithAnnotations('testarb.arb');
// const goldenEditor = await getEditor('testarb.annotated');

// assert.equal(contentWithAnnotations, goldenEditor.document.getText());
await regenerateGolden(contentWithAnnotations);
});
});

const testFolderLocation: string = "/../../../src/test/";

async function regenerateGolden(contentWithAnnotations: string) {
const uri = vscode.Uri.file(path.join(__dirname, testFolderLocation, 'testarb.annotated'));
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(contentWithAnnotations));
}

async function buildContentWithAnnotations(filename: string) {
const editor = await getEditor(filename);
const decorations = new DecoratorAndParser().parseAndDecorate(editor);
const content = editor.document.getText();
const annotationsForLine = new Map<number, string[]>();
for (const entry of decorations?.decorations.entries() ?? []) {
const decorationType = entry[0];
for (const range of entry[1]) {
for (let lineNumber = range.start.line; lineNumber <= range.end.line; lineNumber++) {
const line = editor.document.lineAt(lineNumber);
const offsetInLine = range.start.character - line.range.start.character;
const lengthInLine = (line.range.end.character - range.start.character) - (line.range.end.character - range.end.character);
const annotation = ' '.repeat(offsetInLine) + '^'.repeat(lengthInLine) + annotationNames.get(decorationType)!;
annotationsForLine.set(lineNumber, [...(annotationsForLine.get(lineNumber) ?? []), annotation]);
}
}
}
for (const diagnostic of decorations?.diagnostics ?? []) {
const range = diagnostic.range;
for (let lineNumber = range.start.line; lineNumber <= range.end.line; lineNumber++) {
const line = editor.document.lineAt(lineNumber);
const offsetInLine = range.start.character - line.range.start.character;
const lengthInLine = (line.range.end.character - range.start.character) - (line.range.end.character - range.end.character);
const annotation = ' '.repeat(offsetInLine) + '^'.repeat(lengthInLine) + '[' + vscode.DiagnosticSeverity[diagnostic.severity] + ']:"' + diagnostic.message + '"';
annotationsForLine.set(lineNumber, [...(annotationsForLine.get(lineNumber) ?? []), annotation]);
}
}
const lines = content.split('\n');
const numLines = lines.length;
for (let index = numLines; index > 0; index--) {
if (annotationsForLine.has(index)) {
lines.splice(index + 1, 0, ...annotationsForLine.get(index)!);
}
}
const contentWithAnnotations = lines.join('\n');
return contentWithAnnotations;
}

async function getEditor(filename: string) {
const testFilePath = path.join(__dirname, testFolderLocation, filename);
const uri = vscode.Uri.file(testFilePath);
const document = await vscode.workspace.openTextDocument(uri);
const editor = await vscode.window.showTextDocument(document);
return editor;
}
92 changes: 92 additions & 0 deletions src/test/testarb.annotated
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"@@locale": "en",
"appName": "Demo app",
"pageLog{inUsername": "Your username",
^^^^^^^^^^^^^^^^^^[Error]:"Key "pageLog{inUsername" is not a valid message key."
"@pageLoginUsername": {},
^^^^^^^^^^^^^^^^^^[Error]:"Metadata for an undefined key. Add a message key with the name "pageLoginUsername"."
"pageLoginPassword": "Your password",
"@pageLoginPassword": {},
"pageHomeTitle": "Welcome {firstName}",
^^^^^^^^^[decoration]argument
"@pageHomeTitle": {
"description": "Welcome message on the Home screen",
"placeholders": {
"firstName": {}
^^^^^^^^^[decoration]argument
}
},
"pageHomeInboxCount": "{count, plural, zero{I have {vehicleType, select, sedn{Sedan} cabrolet{Solid roof cabriolet} tuck{16 wheel truck} oter{Other}} no new messages} one{You have 1 new message} other{You have {count} new messages}}",
^^^^^[decoration]argument
^^^^^^^^^^^[decoration]argument
^^^^^[decoration]argument
^^^^^[decoration]select
^^^^^^^^[decoration]select
^^^^[decoration]select
^^^^[decoration]select
^^^^^[decoration]plural
^^^[decoration]plural
^^^^^[decoration]plural
"@pageHomeInboxCount": {
"description": "New messages count on the Home screen",
"placeholders": {
"count": {},
^^^^^[decoration]argument
"vehicleType": {}
^^^^^^^^^^^[decoration]argument
}
},
"pageHomeBirthday": "Today is {sex, select, male{his b'{irthday} female{her birthday} other{their birthday}}.",
^^^[decoration]argument
^^^^^[decoration]select
^^^^^^[decoration]select
^^^^^[decoration]select
"@pageHomeBirthday": {
"description": "Birthday message on the Home screen",
"placeholders": {
"sex": {}
^^^[decoration]argument
}
},
"commonVehicleType": "{vehicleType, s{elect, sedan{Sedan} cabriolet{Solid roof cabriolet} truck{16 wheel truck} other{Other}}",
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[Error]:"Unbalanced curly bracket found. Try escaping the bracket using a single quote ' ."
"@commonVeshicleType": {
^^^^^^^^^^^^^^^^^^^[Error]:"Metadata for an undefined key. Add a message key with the name "commonVeshicleType"."
"description": "Vehicle type",
"placeholders": {
"vehicleType": {}
^^^^^^^^^^^[decoration]argument
^^^^^^^^^^^[Warning]:"Placeholder "vehicleType" is being declared, but not used in message."
}
},
"pageHomeBalance": "Your balance is {am[ount} on {date2}",
^^^^^[decoration]argument
^^^^^^^[Error]:""am[ount" is not a valid argument name."
^^^^^[Warning]:"Placeholder "date2" not defined in the message metadata."
"@pageHomeBalance": {
"placeholders": {
"amount": {
^^^^^^[decoration]argument
^^^^^^[Warning]:"Placeholder "amount" is being declared, but not used in message."
"type": "double",
"format": "currency",
"example": "$1000.00",
"description": "Account balance",
"optionalParameters": {
"decimalDigits": 2,
"name": "USD",
"symbol": "$",
"customPattern": "¤#0.00"
}
},
"date": {
^^^^[decoration]argument
^^^^[Warning]:"Placeholder "date" is being declared, but not used in message."
"type": "DateTime",
"format": "yMd",
"example": "11/10/2021",
"description": "Balance date"
}
}
}
}
File renamed without changes.

0 comments on commit 71ce71e

Please sign in to comment.