Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for incorrect highlighting in verbatim strings #13664

Merged
merged 1 commit into from Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/highlightjs/dist/bicep.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/highlightjs/package.json
Expand Up @@ -26,6 +26,7 @@
"scripts": {
"build": "webpack",
"test": "jest",
"test:fix": "BASELINE_RECORD=true jest",
"lint": "eslint src",
"lint:fix": "eslint src"
}
Expand Down
2 changes: 1 addition & 1 deletion src/highlightjs/src/bicep.ts
Expand Up @@ -83,7 +83,7 @@ const escapeChar: Mode = {
const stringVerbatim: Mode = {
className: 'string',
begin: `'''`,
end: `'''`,
end: `'''${notBefore(`'`)}`,
}

const stringSubstitution: Mode = {
Expand Down
45 changes: 18 additions & 27 deletions src/highlightjs/test/baselines.test.ts
Expand Up @@ -2,26 +2,23 @@
// Licensed under the MIT License.

import { readdirSync, existsSync } from 'fs';
import { readFile, writeFile } from 'fs/promises';
import { readFile } from 'fs/promises';
import path, { dirname, basename, extname } from 'path';
import { env } from 'process';
import { spawnSync } from 'child_process';
import hljs from 'highlight.js';
import bicep, { default as bicepLanguage } from '../src/bicep';
import { default as bicepLanguage } from '../src/bicep';
import { expectFileContents, baselineRecordEnabled } from './utils';

async function writeBaseline(filePath: string) {
async function generateBaseline(filePath: string) {
const baselineBaseName = basename(filePath, extname(filePath));
const baselineFilePath = path.join(dirname(filePath), `${baselineBaseName}.html`);

let diffBefore = '';
const bicepFile = await readFile(filePath, { encoding: 'utf-8' });
try {
diffBefore = await readFile(baselineFilePath, { encoding: 'utf-8' });
} catch {} // ignore and create the baseline file anyway

hljs.registerLanguage('bicep', bicepLanguage);
const result = hljs.highlight(bicepFile, { language: 'bicep' });
const diffAfter = `
const expected = `
<!--
Preview this file by prepending http://htmlpreview.github.io/? to its URL
e.g. http://htmlpreview.github.io/?https://raw.githubusercontent.com/Azure/bicep/main/src/highlightjs/test/baselines/${baselineBaseName}.html
Expand All @@ -37,15 +34,10 @@ ${result.value}
</body>
</html>`;

const output = {
diffBefore: diffBefore.replace(/\r\n/g, '\n'),
diffAfter: diffAfter.replace(/\r\n/g, '\n'),
return {
expected: expected.replace(/\r\n/g, '\n'),
baselineFilePath,
};

await writeFile(baselineFilePath, output.diffAfter, { encoding: 'utf-8' });

return output;
}

const baselinesDir = `${__dirname}/baselines`;
Expand All @@ -56,16 +48,6 @@ const baselineFiles = readdirSync(baselinesDir)

for (const filePath of baselineFiles) {
describe(filePath, () => {
let result = {
baselineFilePath: '',
diffBefore: '',
diffAfter: ''
};

beforeAll(async () => {
result = await writeBaseline(filePath);
});

if (!basename(filePath).startsWith('invalid_')) {
// skip the invalid files - we don't expect them to compile

Expand All @@ -90,8 +72,17 @@ for (const filePath of baselineFiles) {
});
}

it('baseline matches expected', () => {
expect(result.diffBefore).toEqual(result.diffAfter);
it('baseline matches expected', async () => {
const { expected, baselineFilePath } = await generateBaseline(filePath);

await expectFileContents(baselineFilePath, expected);
});
});
}

describe('Test suite', () => {
it('should not succeed if BASELINE_RECORD is set to true', () => {
// This test just ensures the suite doesn't pass in 'record' mode
expect(baselineRecordEnabled).toBeFalsy();
});
});
9 changes: 9 additions & 0 deletions src/highlightjs/test/baselines/strings.bicep
@@ -0,0 +1,9 @@
// evaluates to '\'abc\''
var multilineExtraQuotes = ''''abc''''

// evaluates to '\'\nabc\n\''
var multilineExtraQuotesNewlines = ''''
abc
''''

var multilineSingleLine = '''hello!'''
24 changes: 24 additions & 0 deletions src/highlightjs/test/baselines/strings.html
@@ -0,0 +1,24 @@

<!--
Preview this file by prepending http://htmlpreview.github.io/? to its URL
e.g. http://htmlpreview.github.io/?https://raw.githubusercontent.com/Azure/bicep/main/src/highlightjs/test/baselines/strings.html
-->
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css">
</head>
<body>
<pre class="hljs">
<span class="hljs-comment">// evaluates to &#x27;\&#x27;abc\&#x27;&#x27;</span>
<span class="hljs-variable"><span class="hljs-keyword">var</span></span> <span class="hljs-variable">multilineExtraQuotes</span> = <span class="hljs-string">&#x27;&#x27;&#x27;&#x27;abc&#x27;&#x27;&#x27;&#x27;</span>

<span class="hljs-comment">// evaluates to &#x27;\&#x27;\nabc\n\&#x27;&#x27;</span>
<span class="hljs-variable"><span class="hljs-keyword">var</span></span> <span class="hljs-variable">multilineExtraQuotesNewlines</span> = <span class="hljs-string">&#x27;&#x27;&#x27;&#x27;
abc
&#x27;&#x27;&#x27;&#x27;</span>

<span class="hljs-variable"><span class="hljs-keyword">var</span></span> <span class="hljs-variable">multilineSingleLine</span> = <span class="hljs-string">&#x27;&#x27;&#x27;hello!&#x27;&#x27;&#x27;</span>

</pre>
</body>
</html>
6 changes: 3 additions & 3 deletions src/highlightjs/test/grammar.test.ts
Expand Up @@ -4,6 +4,7 @@
import { existsSync } from "fs";
import { readFile, rm } from "fs/promises";
import { spawnSync } from "child_process";
import { expectFileContents } from "./utils";

const root = `${__dirname}/..`;

Expand All @@ -27,9 +28,8 @@ describe('grammar tests', () => {
});

it('should be up-to-date', async () => {
const savedGrammar = await readFile(grammarPath, { encoding: 'utf8' });

const generatedGrammar = await generateGrammar();
expect(generatedGrammar).toStrictEqual(savedGrammar);

await expectFileContents(grammarPath, generatedGrammar);
}, webpackTestTimeout);
});
18 changes: 18 additions & 0 deletions src/highlightjs/test/utils.ts
@@ -0,0 +1,18 @@
import { existsSync } from "fs";
import { writeFile, mkdir, readFile } from "fs/promises";
import path from "path";

export const baselineRecordEnabled = (process.env['BASELINE_RECORD']?.toLowerCase() === 'true');

export async function expectFileContents(filePath: string, contents: string) {
if (baselineRecordEnabled) {
await mkdir(path.dirname(filePath), { recursive: true });
await writeFile(filePath, contents, 'utf-8');
} else {
// If these assertions fail, use 'npm run test:fix' to replace the baseline files
expect(existsSync(filePath)).toBeTruthy();

const readContents = await readFile(filePath, { encoding: 'utf8' });
expect(contents).toBe(readContents);
}
}
Comment on lines +5 to +18
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic copied from the bicep-types repo, just to simplify updating of baselines.

1 change: 1 addition & 0 deletions src/monarch/package.json
Expand Up @@ -24,6 +24,7 @@
"scripts": {
"build": "tsc -p ./",
"test": "jest",
"test:fix": "BASELINE_RECORD=true jest",
"lint": "eslint src",
"lint:fix": "eslint src"
},
Expand Down
5 changes: 3 additions & 2 deletions src/monarch/src/bicep.ts
Expand Up @@ -4,6 +4,7 @@
import { languages } from 'monaco-editor-core';

const bounded = (text: string) => `\\b${text}\\b`;
const notBefore = (regex: string) => `(?!${regex})`;

const identifierStart = "[_a-zA-Z]";
const identifierContinue = "[_a-zA-Z0-9]";
Expand Down Expand Up @@ -67,7 +68,7 @@ export const BicepLanguage: languages.IMonarchLanguage = {

stringVerbatim: [
{ regex: `(|'|'')[^']`, action: { token: 'string' } },
{ regex: `'''`, action: { token: 'string.quote', next: '@pop' } },
{ regex: `'''${notBefore(`'`)}`, action: { token: 'string.quote', next: '@pop' } },
],

stringLiteral: [
Expand Down Expand Up @@ -98,7 +99,7 @@ export const BicepLanguage: languages.IMonarchLanguage = {

expression: [
{ regex: `'''`, action: { token: 'string.quote', next: '@stringVerbatim' } },
{ regex: `'`, action: { token: 'string.quote', next: '@stringLiteral' } },
{ regex: `'${notBefore(`''`)}`, action: { token: 'string.quote', next: '@stringLiteral' } },
{ regex: numericLiteral, action: { token: 'number' } },
{ regex: identifier, action: {
cases: {
Expand Down
48 changes: 19 additions & 29 deletions src/monarch/test/baselines.test.ts
Expand Up @@ -27,6 +27,7 @@ import { BicepLanguage } from '../src/bicep';
import { editor, languages } from 'monaco-editor-core';
import { escape } from 'html-escaper';
import { env } from 'process';
import { expectFileContents, baselineRecordEnabled } from './utils';

const tokenToHljsClass: Record<string, string | null> = {
'string.bicep': 'string',
Expand All @@ -51,16 +52,11 @@ async function getTokensByLine(content: string) {
}));
}

async function writeBaseline(filePath: string) {
const baselineBaseName = basename(filePath, extname(filePath));
const baselineFilePath = path.join(dirname(filePath), `${baselineBaseName}.html`);
async function generateBaseline(inputFilePath: string) {
const baselineBaseName = basename(inputFilePath, extname(inputFilePath));
const baselineFilePath = path.join(dirname(inputFilePath), `${baselineBaseName}.html`);

let diffBefore = '';
const bicepFile = await readFile(filePath, { encoding: 'utf-8' });
try {
diffBefore = await readFile(baselineFilePath, { encoding: 'utf-8' });
// eslint-disable-next-line no-empty
} catch {} // ignore and create the baseline file anyway
const bicepFile = await readFile(inputFilePath, { encoding: 'utf-8' });

let html = '';
const tokensByLine = await getTokensByLine(bicepFile);
Expand Down Expand Up @@ -95,7 +91,7 @@ async function writeBaseline(filePath: string) {
html += '\n';
}

const diffAfter = `
const expected = `
<!--
Preview this file by prepending http://htmlpreview.github.io/? to its URL
e.g. http://htmlpreview.github.io/?https://raw.githubusercontent.com/Azure/bicep/main/src/monarch/test/baselines/${baselineBaseName}.html
Expand All @@ -111,15 +107,10 @@ ${html}
</body>
</html>`;

const output = {
diffBefore: diffBefore.replace(/\r\n/g, '\n'),
diffAfter: diffAfter.replace(/\r\n/g, '\n'),
return {
expected: expected.replace(/\r\n/g, '\n'),
baselineFilePath,
};

await writeFile(baselineFilePath, output.diffAfter, { encoding: 'utf-8' });

return output;
}

const baselinesDir = `${__dirname}/baselines`;
Expand All @@ -130,16 +121,6 @@ const baselineFiles = readdirSync(baselinesDir)

for (const filePath of baselineFiles) {
describe(`Baseline: ${filePath}`, () => {
let result: {
baselineFilePath: string;
diffBefore: string;
diffAfter: string;
};

beforeAll(async () => {
result = await writeBaseline(filePath);
});

if (!basename(filePath).startsWith('invalid_')) {
// skip the invalid files - we don't expect them to compile

Expand All @@ -164,8 +145,17 @@ for (const filePath of baselineFiles) {
});
}

it('baseline matches expected', () => {
expect(result.diffBefore).toStrictEqual(result.diffAfter);
it('baseline matches expected', async () => {
const { expected, baselineFilePath } = await generateBaseline(filePath);

await expectFileContents(baselineFilePath, expected);
});
});
}

describe('Test suite', () => {
it('should not succeed if BASELINE_RECORD is set to true', () => {
// This test just ensures the suite doesn't pass in 'record' mode
expect(baselineRecordEnabled).toBeFalsy();
});
});
9 changes: 9 additions & 0 deletions src/monarch/test/baselines/strings.bicep
@@ -0,0 +1,9 @@
// evaluates to '\'abc\''
var multilineExtraQuotes = ''''abc''''

// evaluates to '\'\nabc\n\''
var multilineExtraQuotesNewlines = ''''
abc
''''

var multilineSingleLine = '''hello!'''
25 changes: 25 additions & 0 deletions src/monarch/test/baselines/strings.html
@@ -0,0 +1,25 @@

<!--
Preview this file by prepending http://htmlpreview.github.io/? to its URL
e.g. http://htmlpreview.github.io/?https://raw.githubusercontent.com/Azure/bicep/main/src/monarch/test/baselines/strings.html
-->
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css">
</head>
<body>
<pre class="hljs">
<span class="hljs-comment">// evaluates to &#39;\&#39;abc\&#39;&#39;</span>
<span class="hljs-keyword">var</span> <span class="hljs-variable">multilineExtraQuotes</span> = <span class="hljs-string">&#39;&#39;&#39;</span><span class="hljs-string">&#39;abc</span>&#39;<span class="hljs-string">&#39;&#39;&#39;</span>

<span class="hljs-comment">// evaluates to &#39;\&#39;\nabc\n\&#39;&#39;</span>
<span class="hljs-keyword">var</span> <span class="hljs-variable">multilineExtraQuotesNewlines</span> = <span class="hljs-string">&#39;&#39;&#39;</span>&#39;
<span class="hljs-string">abc</span>
&#39;<span class="hljs-string">&#39;&#39;&#39;</span>

<span class="hljs-keyword">var</span> <span class="hljs-variable">multilineSingleLine</span> = <span class="hljs-string">&#39;&#39;&#39;</span><span class="hljs-string">hello!</span><span class="hljs-string">&#39;&#39;&#39;</span>


</pre>
</body>
</html>
18 changes: 18 additions & 0 deletions src/monarch/test/utils.ts
@@ -0,0 +1,18 @@
import { existsSync } from "fs";
import { writeFile, mkdir, readFile } from "fs/promises";
import path from "path";

export const baselineRecordEnabled = (process.env['BASELINE_RECORD']?.toLowerCase() === 'true');

export async function expectFileContents(filePath: string, contents: string) {
if (baselineRecordEnabled) {
await mkdir(path.dirname(filePath), { recursive: true });
await writeFile(filePath, contents, 'utf-8');
} else {
// If these assertions fail, use 'npm run test:fix' to replace the baseline files
expect(existsSync(filePath)).toBeTruthy();

const readContents = await readFile(filePath, { encoding: 'utf8' });
expect(contents).toBe(readContents);
}
}
2 changes: 1 addition & 1 deletion src/textmate/bicep.tmlanguage
Expand Up @@ -361,7 +361,7 @@
<key>begin</key>
<string>'''</string>
<key>end</key>
<string>'''</string>
<string>'''(?!')</string>
<key>patterns</key>
<array/>
</dict>
Expand Down
1 change: 1 addition & 0 deletions src/textmate/package.json
Expand Up @@ -28,6 +28,7 @@
"scripts": {
"build": "ts-node src/cmd/build.ts",
"test": "jest",
"test:fix": "BASELINE_RECORD=true jest",
"lint": "eslint src",
"lint:fix": "eslint src"
}
Expand Down
2 changes: 1 addition & 1 deletion src/textmate/src/bicep.ts
Expand Up @@ -111,7 +111,7 @@ const stringVerbatim: BeginEndRule = {
key: "string-verbatim",
scope: "string.quoted.multi.bicep",
begin: `'''`,
end: `'''`,
end: `'''${notBefore(`'`)}`,
patterns: [],
}

Expand Down