Skip to content

enh(parser) new highlight() API #3053

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

Merged
merged 9 commits into from
Mar 20, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ Language grammar improvements:

Deprecations:

- `highlight(languageName, code, ignoreIllegals, continuation)` deprecated as of 10.7
- Please use the newer API which takes `code` and then accepts options as an object
- IE: `highlight(code, {language, ignoreIllegals})`
- `continuation` is for internal use only and no longer supported

- `highlightBlock(el)` deprecated as of 10.7.
- Please use `highlightElement(el)` instead.
- Plugin callbacks renamed `before/after:highlightBlock` => `before/after:highlightElement`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const hljs = require('highlight.js/lib/core'); // require only the core library
// separately require languages
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));

const highlightedCode = hljs.highlight('xml', '<span>Hello World!</span>').value
const highlightedCode = hljs.highlight('<span>Hello World!</span>', {language: 'xml'}).value
```


Expand Down
25 changes: 17 additions & 8 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ Library API
Highlight.js exports a few functions as methods of the ``hljs`` object.


``highlight(languageName, code, ignore_illegals, continuation)``
----------------------------------------------------------------
``highlight(languageName, code, ignoreIllegals, continuation)`` (deprecated with 10.7)
--------------------------------------------------------------------------------------

Core highlighting function.
Accepts a language name, or an alias, and a string with the code to highlight.
The ``ignore_illegals`` parameter, when present and evaluates to a true value,
forces highlighting to finish even in case of detecting illegal syntax for the
language instead of throwing an exception.
The ``continuation`` is an optional mode stack representing unfinished parsing.
**This is the old API, please see the new API immediately below.**

``continuation`` is an optional mode stack representing unfinished parsing.
When present, the function will restart parsing from this state instead of
initializing a new one. This is used internally for `sublanguage` support.

Expand All @@ -21,6 +18,18 @@ because there is no requirement that a grammar handle linebreaks in any special
way. It's quite possible for a grammar to have a single mode/regex that matches
MANY lines at once. This is not discouraged and entirely up to the grammar.



``highlight(code, {language, ignoreIllegals})``
--------------------------------------------------------------------------------------

Core highlighting function. Accepts the code to highlight (string) and a list of options (object).
The ``language`` parameter must be present and specify the language name or alias
of the grammar to be used for highlighting.
The ``ignoreIllegals`` is an optional parameter than when true forces highlighting
to finish even in case of detecting illegal syntax for the
language instead of throwing an exception.

Returns an object with the following properties:

* ``language``: language name, same as the name passed in ``languageName``, returned for consistency with ``highlightAuto``
Expand Down
41 changes: 31 additions & 10 deletions src/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,14 @@ const HLJS = function(hljs) {
/**
* Core highlighting function.
*
* @param {string} languageName - the language to use for highlighting
* @param {string} code - the code to highlight
* OLD API
* highlight(lang, code, ignoreIllegals, continuation)
*
* NEW API
* highlight(code, {lang, ignoreIllegals})
*
* @param {string} codeOrlanguageName - the language to use for highlighting
* @param {string | HighlightOptions} optionsOrCode - the code to highlight
* @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
* @param {CompiledMode} [continuation] - current continuation mode, if any
*
Expand All @@ -106,7 +112,24 @@ const HLJS = function(hljs) {
* @property {CompiledMode} top - top of the current mode stack
* @property {boolean} illegal - indicates whether any illegal matches were found
*/
function highlight(languageName, code, ignoreIllegals, continuation) {
function highlight(codeOrlanguageName, optionsOrCode, ignoreIllegals, continuation) {
let code = "";
let languageName = "";
if (typeof optionsOrCode === "object") {
code = codeOrlanguageName;
ignoreIllegals = optionsOrCode.ignoreIllegals;
languageName = optionsOrCode.language;
// continuation not supported at all via the new API
// eslint-disable-next-line no-undefined
continuation = undefined;
} else {
// old API
logger.deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated.");
logger.deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277");
languageName = codeOrlanguageName;
code = optionsOrCode;
}

/** @type {BeforeHighlightContext} */
const context = {
code,
Expand All @@ -133,14 +156,12 @@ const HLJS = function(hljs) {
* private highlight that's used internally and does not fire callbacks
*
* @param {string} languageName - the language to use for highlighting
* @param {string} code - the code to highlight
* @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
* @param {CompiledMode} [continuation] - current continuation mode, if any
* @param {string} codeToHighlight - the code to highlight
* @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
* @param {CompiledMode?} [continuation] - current continuation mode, if any
* @returns {HighlightResult} - result of the highlight operation
*/
function _highlight(languageName, code, ignoreIllegals, continuation) {
const codeToHighlight = code;

function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {
/**
* Return keyword data if a match is a keyword
* @param {CompiledMode} mode - current mode
Expand Down Expand Up @@ -708,7 +729,7 @@ const HLJS = function(hljs) {

node = element;
const text = node.textContent;
const result = language ? highlight(language, text, true) : highlightAuto(text);
const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);

// support for v10 API
fire("after:highlightElement", { el: element, result, text });
Expand Down
8 changes: 4 additions & 4 deletions test/api/beginKeywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,25 @@ describe('beginKeywords', () => {

it("should allow subsequence matches to still succeed", () => {
let content = "A.class = self";
let res = hljs.highlight("has-followup", content);
let res = hljs.highlight(content, {language: "has-followup"});
res.value.should.equal('A.<span class="hljs-found">class</span> = self');
});

it("should ignore a preceeding .", () => {
let content = "A.class = self";
let res = hljs.highlight("test", content);
let res = hljs.highlight(content, { language: "test" });
res.value.should.equal('A.class = self');
});

it("should ignore a trailing .", () => {
let content = "class.config = true";
let res = hljs.highlight("test", content);
let res = hljs.highlight(content, { language: "test" });
res.value.should.equal('class.config = true');
});

it('should detect keywords', () => {
let content = "I have a class yes I do.";
let res = hljs.highlight("test", content);
let res = hljs.highlight(content, { language: "test" });
res.value.should.equal('I have a <span class="hljs-keyword">class</span> yes I do.');
});
});
42 changes: 40 additions & 2 deletions test/api/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,47 @@ const hljs = require('../../build');
const should = require('should');

describe('.highlight()', () => {
it('should support ignoreIllegals (old API)', () => {
let code = "float # float";
let result = hljs.highlight("java", code, true);
result.value.should.equal(`<span class="hljs-keyword">float</span> # <span class="hljs-keyword">float</span>`);

code = "float # float";
result = hljs.highlight("java", code, false);
result.value.should.equal("float # float");
result.illegal.should.equal(true);
});
it('should support ignoreIllegals (new API)', () => {
let code = "float # float";
let result = hljs.highlight(code, { language: "java", ignoreIllegals: true });
result.value.should.equal(`<span class="hljs-keyword">float</span> # <span class="hljs-keyword">float</span>`);

code = "float # float";
result = hljs.highlight(code, { language: "java", ignoreIllegals: false });
result.value.should.equal("float # float");
result.illegal.should.equal(true);

// defaults to false
code = "float # float";
result = hljs.highlight(code, { language: "java" });
result.value.should.equal("float # float");
result.illegal.should.equal(true);
});
it('should use new API with options', () => {
const code = "public void moveTo(int x, int y, int z);";
const result = hljs.highlight(code, { language: "java" });

result.value.should.equal(
'<span class="hljs-function"><span class="hljs-keyword">public</span> ' +
'<span class="hljs-keyword">void</span> <span class="hljs-title">moveTo</span>' +
'<span class="hljs-params">(<span class="hljs-keyword">int</span> x, ' +
'<span class="hljs-keyword">int</span> y, ' +
'<span class="hljs-keyword">int</span> z)</span></span>;'
);
});
it('should works without continuation', () => {
const code = "public void moveTo(int x, int y, int z);";
const result = hljs.highlight('java', code, false, false);
const code = "public void moveTo(int x, int y, int z);";
const result = hljs.highlight(code, { language: 'java' });

result.value.should.equal(
'<span class="hljs-function"><span class="hljs-keyword">public</span> ' +
Expand Down
6 changes: 3 additions & 3 deletions test/api/keywords.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('computing the relevance score of a language', () => {
}
const code = "farmer and of river weeds";
hljs.registerLanguage("test", grammar)
const result = hljs.highlight('test', code, false, false);
const result = hljs.highlight(code, { language: 'test' });

result.relevance.should.equal(3)
});
Expand All @@ -27,7 +27,7 @@ describe('computing the relevance score of a language', () => {
}
const code = "farmer and of river weeds";
hljs.registerLanguage("test", grammar)
const result = hljs.highlight('test', code, false, false);
const result = hljs.highlight(code, { language: 'test' });

result.relevance.should.equal(13)
});
Expand All @@ -41,7 +41,7 @@ describe('computing the relevance score of a language', () => {
}
const code = "farmer and of river weeds";
hljs.registerLanguage("test", grammar)
const result = hljs.highlight('test', code, false, false);
const result = hljs.highlight(code, { language: 'test' });

result.relevance.should.equal(4)
});
Expand Down
2 changes: 1 addition & 1 deletion test/browser/plain.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('plain browser', function() {

it('should return relevance key', async function() {
await buildFakeDOM.bind(this, defaultCase)();
var out = this.hljs.highlight("javascript","");
var out = this.hljs.highlight("", { language: "javascript" });
out.relevance.should.equal(0);
});

Expand Down
2 changes: 1 addition & 1 deletion test/browser/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('web worker', function() {
importScripts(event.data.script);
postMessage(1);
} else {
var result = hljs.highlight('javascript', event.data);
var result = hljs.highlight(event.data, { language: 'javascript' });
postMessage(result.value);
}
};
Expand Down
2 changes: 1 addition & 1 deletion test/detect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ describe('hljs.highlightAuto()', () => {

it("compiling the grammars", async function() {
const languages = hljs.listLanguages();
languages.forEach(l => hljs.highlight(l, ""))
languages.forEach(lang => hljs.highlight("", { language: lang} ))
}); // this is also required for the dynamic test generation above to work
});
2 changes: 1 addition & 1 deletion test/markup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function testLanguage(language, {testDir}) {
const expectedFile = fs.readFile(filename, 'utf-8');

Promise.all([sourceFile, expectedFile]).then(function([source, expected]) {
const actual = hljs.highlight(language, source).value;
const actual = hljs.highlight(source, { language }).value;

// Uncomment this for major changes that rewrite the test expectations
// which will then need to be manually compared by hand of course
Expand Down
2 changes: 1 addition & 1 deletion test/parser/compiler-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe.skip("compiler extension plugins", function() {
hljs.addPlugin(plugin);
// stub highlight to make sure the language gets compiled
// since we have no API point to make that happen
hljs.highlight("extension_test", "");
hljs.highlight("", { language: "extension_test" });
const [first, second] = hljs.getLanguage("extension_test").contains;
this.first = first;
this.second = second;
Expand Down
2 changes: 1 addition & 1 deletion test/parser/look-ahead-end-matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("parser specifics", function () {
};
});

hljs.highlight('test-language', 'ABC123 is the secret. XYZ123. End of string: ABC123').value
hljs.highlight('ABC123 is the secret. XYZ123. End of string: ABC123', {language: 'test-language'}).value
.should.equal(
// one full match at beginning, other match begins with XYZ but then never terminates,
// so the end of the parsing finally closes the span tag
Expand Down
4 changes: 2 additions & 2 deletions test/parser/resume-scan.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ hljs.debugMode(); // tests run in debug mode so errors are raised
describe("bugs", function() {
describe("resume scan when a match is ignored", () => {
it("should continue to highlight later matches", () => {
const result = hljs.highlight('java', 'ImmutablePair.of(Stuff.class, "bar")');
const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar")', {language: 'java'});
result.value.should.equal(
'ImmutablePair.of(Stuff.class, <span class="hljs-string">&quot;bar&quot;</span>)'
);
Expand All @@ -16,7 +16,7 @@ describe("bugs", function() {
// rule we really only want to skip searching for THAT rule at that same location, we
// do not want to stop searching for ALL the prior rules at that location...
it("BUT should not skip ahead too far", () => {
const result = hljs.highlight('java', 'ImmutablePair.of(Stuff.class, "bar");\n23');
const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar");\n23', {language: 'java'});
result.value.should.equal(
'ImmutablePair.of(Stuff.class, <span class="hljs-string">&quot;bar&quot;</span>);\n' +
'<span class="hljs-number">23</span>'
Expand Down
2 changes: 1 addition & 1 deletion test/parser/reuse-endsWithParent.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("bugs", function () {
};
});

hljs.highlight('test-language', '(abc 123) [abc 123] (abc 123)').value
hljs.highlight('(abc 123) [abc 123] (abc 123)', {language: 'test-language'}).value
.should.equal(
'<span class="hljs-tag">(<span class="hljs-name">abc</span> 123)</span> ' +
'<span class="hljs-tag">[<span class="hljs-name">abc</span> 123]</span> ' +
Expand Down
20 changes: 10 additions & 10 deletions test/parser/should-not-destroyData.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ describe("parser/should not destroy data", function () {
};
});

hljs.highlight('test-language', 'The number is 123_longint yes.').value
hljs.highlight('The number is 123_longint yes.', {language: 'test-language' }).value
.should.equal(
// The whole number isn't highlighted (the rule doesn't handle the _type)
// But the key thing is the "1" is registered as a match for the rule
// instead of disappearing from the output completely, which is what
// would happen previously.
'The number is <span class="hljs-number">1</span>23_longint yes.'
// Incorrect prior output:
// 'The number is <span class="hljs-number"></span>23_longint yes.'
)
// The whole number isn't highlighted (the rule doesn't handle the _type)
// But the key thing is the "1" is registered as a match for the rule
// instead of disappearing from the output completely, which is what
// would happen previously.
'The number is <span class="hljs-number">1</span>23_longint yes.'
// Incorrect prior output:
// 'The number is <span class="hljs-number"></span>23_longint yes.'
);
hljs.debugMode();
should(() => {
hljs.highlight('test-language', 'The number is 123_longint yes.').value
hljs.highlight('The number is 123_longint yes.', {language: 'test-language'}).value
}).throw(Error, {
message: "0 width match regex",
languageName: "test-language"})
Expand Down
2 changes: 1 addition & 1 deletion test/regex/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const polyBacktrackingCache = {};
function retrieveRules(language, { name }) {
// first we need to get the language compiled so we have
// access to the raw regex
hljs.highlight(name, "");
hljs.highlight("", {language: name});
return regexFor(language, { context: name, depth: 0 });
}

Expand Down
5 changes: 5 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ interface EmitterConstructor {
new (opts: any): Emitter
}

interface HighlightOptions {
language: string
ignoreIllegals?: boolean
}

interface HLJSOptions {
noHighlightRe: RegExp
languageDetectRe: RegExp
Expand Down