From 86510e3b20da21b6e1841b8f94b0481569736b40 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Wed, 17 Mar 2021 13:56:33 -0400 Subject: [PATCH 1/7] (chore) new highlight() API --- CHANGES.md | 5 +++++ docs/api.rst | 25 +++++++++++++++++-------- src/highlight.js | 23 +++++++++++++++++------ test/api/highlight.js | 40 +++++++++++++++++++++++++++++++++++++++- types/index.d.ts | 6 ++++++ 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3b980b62ab..0b1d3d25c7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,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` diff --git a/docs/api.rst b/docs/api.rst index 3c2936b0d4..c3f28cb13e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -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. @@ -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`` diff --git a/src/highlight.js b/src/highlight.js index 49d974af18..11d7fa226b 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -132,14 +132,25 @@ 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} codeOrlanguageName - the language to use for highlighting + * @param {string | HighlightOptions} codeOrOptions - 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(codeOrlanguageName, codeOrOptions, ignoreIllegals, continuation) { + let codeToHighlight = ""; + let languageName = ""; + if (typeof codeOrOptions === "object") { + const opts = codeOrOptions; + codeToHighlight = codeOrlanguageName; + ignoreIllegals = opts.ignoreIllegals; + continuation = opts.continuation; + languageName = opts.language; + } else { + languageName = codeOrlanguageName; + codeToHighlight = codeOrOptions; + } /** * Return keyword data if a match is a keyword diff --git a/test/api/highlight.js b/test/api/highlight.js index 962c36e175..e2bba1b227 100644 --- a/test/api/highlight.js +++ b/test/api/highlight.js @@ -4,8 +4,46 @@ 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(`float # float`); + + 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(`float # float`); + + 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( + 'public ' + + 'void moveTo' + + '(int x, ' + + 'int y, ' + + 'int z);' + ); + }); it('should works without continuation', () => { - const code = "public void moveTo(int x, int y, int z);"; + const code = "public void moveTo(int x, int y, int z);"; const result = hljs.highlight('java', code, false, false); result.value.should.equal( diff --git a/types/index.d.ts b/types/index.d.ts index 96b14965c6..f848652313 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -112,6 +112,12 @@ interface EmitterConstructor { new (opts: any): Emitter } +interface HighlightOptions { + language: string + ignoreIllegals?: boolean + continuation?: CompiledMode +} + interface HLJSOptions { noHighlightRe: RegExp languageDetectRe: RegExp From 329a62368cc5e49e8f59d906abfd50f309e7dc5c Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Wed, 17 Mar 2021 22:14:29 -0400 Subject: [PATCH 2/7] update README example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b90a6bda15..55954943a4 100644 --- a/README.md +++ b/README.md @@ -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', 'Hello World!').value +const highlightedCode = hljs.highlight('Hello World!', {language: 'xml'}).value ``` From da1063d0a3d242df00ecac19f11b2f77e1027402 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sat, 20 Mar 2021 00:45:51 -0400 Subject: [PATCH 3/7] deprecation notice and fix the correct function name --- src/highlight.js | 50 +++++++++++++++----------- test/api/beginKeywords.js | 8 ++--- test/api/highlight.js | 2 +- test/api/keywords.js | 6 ++-- test/browser/plain.js | 2 +- test/browser/worker.js | 2 +- test/detect/index.js | 2 +- test/markup/index.js | 2 +- test/parser/compiler-extensions.js | 2 +- test/parser/look-ahead-end-matchers.js | 2 +- test/parser/resume-scan.js | 4 +-- test/parser/reuse-endsWithParent.js | 2 +- test/parser/should-not-destroyData.js | 20 +++++------ test/regex/index.js | 2 +- 14 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/highlight.js b/src/highlight.js index 11d7fa226b..1173e72359 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -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 * @@ -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; + continuation = optionsOrCode.continuation; + languageName = optionsOrCode.language; + } else { + // old API + var stack = new Error().stack; + console.log( stack ) + 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, @@ -132,26 +155,13 @@ const HLJS = function(hljs) { /** * private highlight that's used internally and does not fire callbacks * - * @param {string} codeOrlanguageName - the language to use for highlighting - * @param {string | HighlightOptions} codeOrOptions - the code to highlight + * @param {string} languageName - the language to use for highlighting + * @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(codeOrlanguageName, codeOrOptions, ignoreIllegals, continuation) { - let codeToHighlight = ""; - let languageName = ""; - if (typeof codeOrOptions === "object") { - const opts = codeOrOptions; - codeToHighlight = codeOrlanguageName; - ignoreIllegals = opts.ignoreIllegals; - continuation = opts.continuation; - languageName = opts.language; - } else { - languageName = codeOrlanguageName; - codeToHighlight = codeOrOptions; - } - + function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { /** * Return keyword data if a match is a keyword * @param {CompiledMode} mode - current mode @@ -719,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 }); diff --git a/test/api/beginKeywords.js b/test/api/beginKeywords.js index 53bf85b796..407f830db6 100644 --- a/test/api/beginKeywords.js +++ b/test/api/beginKeywords.js @@ -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.class = 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 class yes I do.'); }); }); diff --git a/test/api/highlight.js b/test/api/highlight.js index e2bba1b227..043dc499ad 100644 --- a/test/api/highlight.js +++ b/test/api/highlight.js @@ -44,7 +44,7 @@ describe('.highlight()', () => { }); 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 result = hljs.highlight(code, { language: 'java' }); result.value.should.equal( 'public ' + diff --git a/test/api/keywords.js b/test/api/keywords.js index f72a212c08..de1c466a3b 100644 --- a/test/api/keywords.js +++ b/test/api/keywords.js @@ -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) }); @@ -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) }); @@ -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) }); diff --git a/test/browser/plain.js b/test/browser/plain.js index 0416e94d3f..ca6ef222fb 100644 --- a/test/browser/plain.js +++ b/test/browser/plain.js @@ -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); }); diff --git a/test/browser/worker.js b/test/browser/worker.js index c1e99ca64e..af8dcc2f0d 100644 --- a/test/browser/worker.js +++ b/test/browser/worker.js @@ -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); } }; diff --git a/test/detect/index.js b/test/detect/index.js index b51adb10b3..cc317e2e9f 100644 --- a/test/detect/index.js +++ b/test/detect/index.js @@ -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 }); diff --git a/test/markup/index.js b/test/markup/index.js index 28138ffa6b..f653976082 100644 --- a/test/markup/index.js +++ b/test/markup/index.js @@ -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 diff --git a/test/parser/compiler-extensions.js b/test/parser/compiler-extensions.js index 90b1d0f37e..7ba26e0199 100644 --- a/test/parser/compiler-extensions.js +++ b/test/parser/compiler-extensions.js @@ -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; diff --git a/test/parser/look-ahead-end-matchers.js b/test/parser/look-ahead-end-matchers.js index 241e1fd0f6..5d96e1d8ef 100644 --- a/test/parser/look-ahead-end-matchers.js +++ b/test/parser/look-ahead-end-matchers.js @@ -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 diff --git a/test/parser/resume-scan.js b/test/parser/resume-scan.js index fb50a47f37..2e4d6c208a 100644 --- a/test/parser/resume-scan.js +++ b/test/parser/resume-scan.js @@ -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, "bar")' ); @@ -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, "bar");\n' + '23' diff --git a/test/parser/reuse-endsWithParent.js b/test/parser/reuse-endsWithParent.js index 7a4d2fe9dc..d8b8f8e621 100644 --- a/test/parser/reuse-endsWithParent.js +++ b/test/parser/reuse-endsWithParent.js @@ -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( '(abc 123) ' + '[abc 123] ' + diff --git a/test/parser/should-not-destroyData.js b/test/parser/should-not-destroyData.js index e12a571ed0..d5688112ee 100644 --- a/test/parser/should-not-destroyData.js +++ b/test/parser/should-not-destroyData.js @@ -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 123_longint yes.' - // Incorrect prior output: - // 'The number is 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 123_longint yes.' + // Incorrect prior output: + // 'The number is 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"}) diff --git a/test/regex/index.js b/test/regex/index.js index 426568ba4f..29c29f0d22 100644 --- a/test/regex/index.js +++ b/test/regex/index.js @@ -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 }); } From b954e52c71c77c411a60b9474ae199b3f016607d Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sat, 20 Mar 2021 00:54:06 -0400 Subject: [PATCH 4/7] remove logging --- src/highlight.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/highlight.js b/src/highlight.js index 1173e72359..258f54e9d8 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -122,8 +122,6 @@ const HLJS = function(hljs) { languageName = optionsOrCode.language; } else { // old API - var stack = new Error().stack; - console.log( stack ) 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; From 887ce9cd1b7e90fc789ff2b489e643b1621fdf6c Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sat, 20 Mar 2021 00:55:50 -0400 Subject: [PATCH 5/7] continuation may not be passed via option --- src/highlight.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/highlight.js b/src/highlight.js index 258f54e9d8..2b8bc90187 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -118,7 +118,6 @@ const HLJS = function(hljs) { if (typeof optionsOrCode === "object") { code = codeOrlanguageName; ignoreIllegals = optionsOrCode.ignoreIllegals; - continuation = optionsOrCode.continuation; languageName = optionsOrCode.language; } else { // old API From d46d9a77cd07965a4a2d32a6fd37d08a6763c06f Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sat, 20 Mar 2021 02:09:50 -0400 Subject: [PATCH 6/7] remove from api --- types/index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index f848652313..4f08561889 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -115,7 +115,6 @@ interface EmitterConstructor { interface HighlightOptions { language: string ignoreIllegals?: boolean - continuation?: CompiledMode } interface HLJSOptions { From b1a71dadb7263d3c1973940afe18655e997e69ca Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sat, 20 Mar 2021 02:28:06 -0400 Subject: [PATCH 7/7] make sure someone does not pass as 4th argument --- src/highlight.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/highlight.js b/src/highlight.js index 2b8bc90187..36bf876ae8 100644 --- a/src/highlight.js +++ b/src/highlight.js @@ -119,6 +119,9 @@ const HLJS = function(hljs) { 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.");