Skip to content

Commit bfb5a59

Browse files
authored
enh(parser) new highlight() API (#3053)
1 parent 6f24850 commit bfb5a59

18 files changed

+126
-48
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ Language grammar improvements:
3535

3636
Deprecations:
3737

38+
- `highlight(languageName, code, ignoreIllegals, continuation)` deprecated as of 10.7
39+
- Please use the newer API which takes `code` and then accepts options as an object
40+
- IE: `highlight(code, {language, ignoreIllegals})`
41+
- `continuation` is for internal use only and no longer supported
42+
3843
- `highlightBlock(el)` deprecated as of 10.7.
3944
- Please use `highlightElement(el)` instead.
4045
- Plugin callbacks renamed `before/after:highlightBlock` => `before/after:highlightElement`

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ const hljs = require('highlight.js/lib/core'); // require only the core library
211211
// separately require languages
212212
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
213213

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

217217

docs/api.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@ Library API
44
Highlight.js exports a few functions as methods of the ``hljs`` object.
55

66

7-
``highlight(languageName, code, ignore_illegals, continuation)``
8-
----------------------------------------------------------------
7+
``highlight(languageName, code, ignoreIllegals, continuation)`` (deprecated with 10.7)
8+
--------------------------------------------------------------------------------------
99

10-
Core highlighting function.
11-
Accepts a language name, or an alias, and a string with the code to highlight.
12-
The ``ignore_illegals`` parameter, when present and evaluates to a true value,
13-
forces highlighting to finish even in case of detecting illegal syntax for the
14-
language instead of throwing an exception.
15-
The ``continuation`` is an optional mode stack representing unfinished parsing.
10+
**This is the old API, please see the new API immediately below.**
11+
12+
``continuation`` is an optional mode stack representing unfinished parsing.
1613
When present, the function will restart parsing from this state instead of
1714
initializing a new one. This is used internally for `sublanguage` support.
1815

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

21+
22+
23+
``highlight(code, {language, ignoreIllegals})``
24+
--------------------------------------------------------------------------------------
25+
26+
Core highlighting function. Accepts the code to highlight (string) and a list of options (object).
27+
The ``language`` parameter must be present and specify the language name or alias
28+
of the grammar to be used for highlighting.
29+
The ``ignoreIllegals`` is an optional parameter than when true forces highlighting
30+
to finish even in case of detecting illegal syntax for the
31+
language instead of throwing an exception.
32+
2433
Returns an object with the following properties:
2534

2635
* ``language``: language name, same as the name passed in ``languageName``, returned for consistency with ``highlightAuto``

src/highlight.js

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,14 @@ const HLJS = function(hljs) {
9393
/**
9494
* Core highlighting function.
9595
*
96-
* @param {string} languageName - the language to use for highlighting
97-
* @param {string} code - the code to highlight
96+
* OLD API
97+
* highlight(lang, code, ignoreIllegals, continuation)
98+
*
99+
* NEW API
100+
* highlight(code, {lang, ignoreIllegals})
101+
*
102+
* @param {string} codeOrlanguageName - the language to use for highlighting
103+
* @param {string | HighlightOptions} optionsOrCode - the code to highlight
98104
* @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
99105
* @param {CompiledMode} [continuation] - current continuation mode, if any
100106
*
@@ -106,7 +112,24 @@ const HLJS = function(hljs) {
106112
* @property {CompiledMode} top - top of the current mode stack
107113
* @property {boolean} illegal - indicates whether any illegal matches were found
108114
*/
109-
function highlight(languageName, code, ignoreIllegals, continuation) {
115+
function highlight(codeOrlanguageName, optionsOrCode, ignoreIllegals, continuation) {
116+
let code = "";
117+
let languageName = "";
118+
if (typeof optionsOrCode === "object") {
119+
code = codeOrlanguageName;
120+
ignoreIllegals = optionsOrCode.ignoreIllegals;
121+
languageName = optionsOrCode.language;
122+
// continuation not supported at all via the new API
123+
// eslint-disable-next-line no-undefined
124+
continuation = undefined;
125+
} else {
126+
// old API
127+
logger.deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated.");
128+
logger.deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277");
129+
languageName = codeOrlanguageName;
130+
code = optionsOrCode;
131+
}
132+
110133
/** @type {BeforeHighlightContext} */
111134
const context = {
112135
code,
@@ -133,14 +156,12 @@ const HLJS = function(hljs) {
133156
* private highlight that's used internally and does not fire callbacks
134157
*
135158
* @param {string} languageName - the language to use for highlighting
136-
* @param {string} code - the code to highlight
137-
* @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
138-
* @param {CompiledMode} [continuation] - current continuation mode, if any
159+
* @param {string} codeToHighlight - the code to highlight
160+
* @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
161+
* @param {CompiledMode?} [continuation] - current continuation mode, if any
139162
* @returns {HighlightResult} - result of the highlight operation
140163
*/
141-
function _highlight(languageName, code, ignoreIllegals, continuation) {
142-
const codeToHighlight = code;
143-
164+
function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {
144165
/**
145166
* Return keyword data if a match is a keyword
146167
* @param {CompiledMode} mode - current mode
@@ -708,7 +729,7 @@ const HLJS = function(hljs) {
708729

709730
node = element;
710731
const text = node.textContent;
711-
const result = language ? highlight(language, text, true) : highlightAuto(text);
732+
const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);
712733

713734
// support for v10 API
714735
fire("after:highlightElement", { el: element, result, text });

test/api/beginKeywords.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,25 @@ describe('beginKeywords', () => {
2727

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

3434
it("should ignore a preceeding .", () => {
3535
let content = "A.class = self";
36-
let res = hljs.highlight("test", content);
36+
let res = hljs.highlight(content, { language: "test" });
3737
res.value.should.equal('A.class = self');
3838
});
3939

4040
it("should ignore a trailing .", () => {
4141
let content = "class.config = true";
42-
let res = hljs.highlight("test", content);
42+
let res = hljs.highlight(content, { language: "test" });
4343
res.value.should.equal('class.config = true');
4444
});
4545

4646
it('should detect keywords', () => {
4747
let content = "I have a class yes I do.";
48-
let res = hljs.highlight("test", content);
48+
let res = hljs.highlight(content, { language: "test" });
4949
res.value.should.equal('I have a <span class="hljs-keyword">class</span> yes I do.');
5050
});
5151
});

test/api/highlight.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,47 @@ const hljs = require('../../build');
44
const should = require('should');
55

66
describe('.highlight()', () => {
7+
it('should support ignoreIllegals (old API)', () => {
8+
let code = "float # float";
9+
let result = hljs.highlight("java", code, true);
10+
result.value.should.equal(`<span class="hljs-keyword">float</span> # <span class="hljs-keyword">float</span>`);
11+
12+
code = "float # float";
13+
result = hljs.highlight("java", code, false);
14+
result.value.should.equal("float # float");
15+
result.illegal.should.equal(true);
16+
});
17+
it('should support ignoreIllegals (new API)', () => {
18+
let code = "float # float";
19+
let result = hljs.highlight(code, { language: "java", ignoreIllegals: true });
20+
result.value.should.equal(`<span class="hljs-keyword">float</span> # <span class="hljs-keyword">float</span>`);
21+
22+
code = "float # float";
23+
result = hljs.highlight(code, { language: "java", ignoreIllegals: false });
24+
result.value.should.equal("float # float");
25+
result.illegal.should.equal(true);
26+
27+
// defaults to false
28+
code = "float # float";
29+
result = hljs.highlight(code, { language: "java" });
30+
result.value.should.equal("float # float");
31+
result.illegal.should.equal(true);
32+
});
33+
it('should use new API with options', () => {
34+
const code = "public void moveTo(int x, int y, int z);";
35+
const result = hljs.highlight(code, { language: "java" });
36+
37+
result.value.should.equal(
38+
'<span class="hljs-function"><span class="hljs-keyword">public</span> ' +
39+
'<span class="hljs-keyword">void</span> <span class="hljs-title">moveTo</span>' +
40+
'<span class="hljs-params">(<span class="hljs-keyword">int</span> x, ' +
41+
'<span class="hljs-keyword">int</span> y, ' +
42+
'<span class="hljs-keyword">int</span> z)</span></span>;'
43+
);
44+
});
745
it('should works without continuation', () => {
8-
const code = "public void moveTo(int x, int y, int z);";
9-
const result = hljs.highlight('java', code, false, false);
46+
const code = "public void moveTo(int x, int y, int z);";
47+
const result = hljs.highlight(code, { language: 'java' });
1048

1149
result.value.should.equal(
1250
'<span class="hljs-function"><span class="hljs-keyword">public</span> ' +

test/api/keywords.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('computing the relevance score of a language', () => {
1313
}
1414
const code = "farmer and of river weeds";
1515
hljs.registerLanguage("test", grammar)
16-
const result = hljs.highlight('test', code, false, false);
16+
const result = hljs.highlight(code, { language: 'test' });
1717

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

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

4646
result.relevance.should.equal(4)
4747
});

test/browser/plain.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('plain browser', function() {
1616

1717
it('should return relevance key', async function() {
1818
await buildFakeDOM.bind(this, defaultCase)();
19-
var out = this.hljs.highlight("javascript","");
19+
var out = this.hljs.highlight("", { language: "javascript" });
2020
out.relevance.should.equal(0);
2121
});
2222

test/browser/worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('web worker', function() {
1313
importScripts(event.data.script);
1414
postMessage(1);
1515
} else {
16-
var result = hljs.highlight('javascript', event.data);
16+
var result = hljs.highlight(event.data, { language: 'javascript' });
1717
postMessage(result.value);
1818
}
1919
};

test/detect/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ describe('hljs.highlightAuto()', () => {
5656

5757
it("compiling the grammars", async function() {
5858
const languages = hljs.listLanguages();
59-
languages.forEach(l => hljs.highlight(l, ""))
59+
languages.forEach(lang => hljs.highlight("", { language: lang} ))
6060
}); // this is also required for the dynamic test generation above to work
6161
});

test/markup/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function testLanguage(language, {testDir}) {
2727
const expectedFile = fs.readFile(filename, 'utf-8');
2828

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

3232
// Uncomment this for major changes that rewrite the test expectations
3333
// which will then need to be manually compared by hand of course

test/parser/compiler-extensions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe.skip("compiler extension plugins", function() {
2828
hljs.addPlugin(plugin);
2929
// stub highlight to make sure the language gets compiled
3030
// since we have no API point to make that happen
31-
hljs.highlight("extension_test", "");
31+
hljs.highlight("", { language: "extension_test" });
3232
const [first, second] = hljs.getLanguage("extension_test").contains;
3333
this.first = first;
3434
this.second = second;

test/parser/look-ahead-end-matchers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe("parser specifics", function () {
2020
};
2121
});
2222

23-
hljs.highlight('test-language', 'ABC123 is the secret. XYZ123. End of string: ABC123').value
23+
hljs.highlight('ABC123 is the secret. XYZ123. End of string: ABC123', {language: 'test-language'}).value
2424
.should.equal(
2525
// one full match at beginning, other match begins with XYZ but then never terminates,
2626
// so the end of the parsing finally closes the span tag

test/parser/resume-scan.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ hljs.debugMode(); // tests run in debug mode so errors are raised
66
describe("bugs", function() {
77
describe("resume scan when a match is ignored", () => {
88
it("should continue to highlight later matches", () => {
9-
const result = hljs.highlight('java', 'ImmutablePair.of(Stuff.class, "bar")');
9+
const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar")', {language: 'java'});
1010
result.value.should.equal(
1111
'ImmutablePair.of(Stuff.class, <span class="hljs-string">&quot;bar&quot;</span>)'
1212
);
@@ -16,7 +16,7 @@ describe("bugs", function() {
1616
// rule we really only want to skip searching for THAT rule at that same location, we
1717
// do not want to stop searching for ALL the prior rules at that location...
1818
it("BUT should not skip ahead too far", () => {
19-
const result = hljs.highlight('java', 'ImmutablePair.of(Stuff.class, "bar");\n23');
19+
const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar");\n23', {language: 'java'});
2020
result.value.should.equal(
2121
'ImmutablePair.of(Stuff.class, <span class="hljs-string">&quot;bar&quot;</span>);\n' +
2222
'<span class="hljs-number">23</span>'

test/parser/reuse-endsWithParent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("bugs", function () {
1616
};
1717
});
1818

19-
hljs.highlight('test-language', '(abc 123) [abc 123] (abc 123)').value
19+
hljs.highlight('(abc 123) [abc 123] (abc 123)', {language: 'test-language'}).value
2020
.should.equal(
2121
'<span class="hljs-tag">(<span class="hljs-name">abc</span> 123)</span> ' +
2222
'<span class="hljs-tag">[<span class="hljs-name">abc</span> 123]</span> ' +

test/parser/should-not-destroyData.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@ describe("parser/should not destroy data", function () {
1919
};
2020
});
2121

22-
hljs.highlight('test-language', 'The number is 123_longint yes.').value
22+
hljs.highlight('The number is 123_longint yes.', {language: 'test-language' }).value
2323
.should.equal(
24-
// The whole number isn't highlighted (the rule doesn't handle the _type)
25-
// But the key thing is the "1" is registered as a match for the rule
26-
// instead of disappearing from the output completely, which is what
27-
// would happen previously.
28-
'The number is <span class="hljs-number">1</span>23_longint yes.'
29-
// Incorrect prior output:
30-
// 'The number is <span class="hljs-number"></span>23_longint yes.'
31-
)
24+
// The whole number isn't highlighted (the rule doesn't handle the _type)
25+
// But the key thing is the "1" is registered as a match for the rule
26+
// instead of disappearing from the output completely, which is what
27+
// would happen previously.
28+
'The number is <span class="hljs-number">1</span>23_longint yes.'
29+
// Incorrect prior output:
30+
// 'The number is <span class="hljs-number"></span>23_longint yes.'
31+
);
3232
hljs.debugMode();
3333
should(() => {
34-
hljs.highlight('test-language', 'The number is 123_longint yes.').value
34+
hljs.highlight('The number is 123_longint yes.', {language: 'test-language'}).value
3535
}).throw(Error, {
3636
message: "0 width match regex",
3737
languageName: "test-language"})

test/regex/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const polyBacktrackingCache = {};
2525
function retrieveRules(language, { name }) {
2626
// first we need to get the language compiled so we have
2727
// access to the raw regex
28-
hljs.highlight(name, "");
28+
hljs.highlight("", {language: name});
2929
return regexFor(language, { context: name, depth: 0 });
3030
}
3131

types/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ interface EmitterConstructor {
112112
new (opts: any): Emitter
113113
}
114114

115+
interface HighlightOptions {
116+
language: string
117+
ignoreIllegals?: boolean
118+
}
119+
115120
interface HLJSOptions {
116121
noHighlightRe: RegExp
117122
languageDetectRe: RegExp

0 commit comments

Comments
 (0)