diff --git a/CHANGES.md b/CHANGES.md index ac98114f38..2a09c4c6a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,8 @@ Parser: Grammars: +- fix(ts) some complex types would classify as JSX (#3278) [Josh Goebel][] +- fix(js/ts) less false positives for `class X extends Y` (#3278) [Josh Goebel][] - enh(css): add properties from several W3C (Candidate) Recommendations (#3308) - fix(js/ts) `Float32Array` highlighted incorrectly (#3353) [Josh Goebel][] - fix(css) single-colon psuedo-elements no longer break highlighting (#3240) [Josh Goebel][] diff --git a/src/languages/javascript.js b/src/languages/javascript.js index 6dc7bd521b..b09065dd85 100644 --- a/src/languages/javascript.js +++ b/src/languages/javascript.js @@ -28,6 +28,8 @@ export default function(hljs) { begin: '<>', end: '' }; + // to avoid some special cases inside isTrulyOpeningTag + const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/; const XML_TAG = { begin: /<[A-Za-z0-9\\._:-]+/, end: /\/[A-Za-z0-9\\._:-]+>|\/>/, @@ -38,15 +40,20 @@ export default function(hljs) { isTrulyOpeningTag: (match, response) => { const afterMatchIndex = match[0].length + match.index; const nextChar = match.input[afterMatchIndex]; - // nested type? - // HTML should not include another raw `<` inside a tag - // But a type might: `>`, etc. - if (nextChar === "<") { + if ( + // HTML should not include another raw `<` inside a tag + // nested type? + // `>`, etc. + nextChar === "<" || + // the , gives away that this is not HTML + // `` + nextChar === ",") { response.ignoreMatch(); return; } - // - // This is now either a tag or a type. + + // `` + // Quite possibly a tag, lets look for a matching closing tag... if (nextChar === ">") { // if we cannot find a matching closing tag, then we // will ignore it @@ -54,6 +61,22 @@ export default function(hljs) { response.ignoreMatch(); } } + + // `` (self-closing) + // handled by simpleSelfClosing rule + + // `` + // technically this could be HTML, but it smells like a type + let m; + const afterMatch = match.input.substr(afterMatchIndex); + // NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276 + if ((m = afterMatch.match(/^\s+extends\s+/))) { + if (m.index === 0) { + response.ignoreMatch(); + // eslint-disable-next-line no-useless-return + return; + } + } } }; const KEYWORDS = { @@ -227,28 +250,37 @@ export default function(hljs) { // ES6 classes const CLASS_OR_EXTENDS = { variants: [ + // class Car extends vehicle { match: [ /class/, /\s+/, - IDENT_RE + IDENT_RE, + /\s+/, + /extends/, + /\s+/, + regex.concat(IDENT_RE, "(", regex.concat(/\./, IDENT_RE), ")*") ], scope: { 1: "keyword", - 3: "title.class" + 3: "title.class", + 5: "keyword", + 7: "title.class.inherited" } }, + // class Car { match: [ - /extends/, + /class/, /\s+/, - regex.concat(IDENT_RE, "(", regex.concat(/\./, IDENT_RE), ")*") + IDENT_RE ], scope: { 1: "keyword", - 3: "title.class.inherited" + 3: "title.class" } - } + }, + ] }; @@ -390,7 +422,7 @@ export default function(hljs) { aliases: ['js', 'jsx', 'mjs', 'cjs'], keywords: KEYWORDS, // this will be extended by TypeScript - exports: { PARAMS_CONTAINS }, + exports: { PARAMS_CONTAINS, CLASS_REFERENCE }, illegal: /#(?![$_A-z])/, contains: [ hljs.SHEBANG({ @@ -464,6 +496,7 @@ export default function(hljs) { { // JSX variants: [ { begin: FRAGMENT.begin, end: FRAGMENT.end }, + { match: XML_SELF_CLOSING }, { begin: XML_TAG.begin, // we carefully check the opening tag to see if it truly diff --git a/src/languages/typescript.js b/src/languages/typescript.js index eea7ad8967..c63adc995c 100644 --- a/src/languages/typescript.js +++ b/src/languages/typescript.js @@ -12,19 +12,9 @@ import javascript from "./javascript.js"; /** @type LanguageFn */ export default function(hljs) { + const tsLanguage = javascript(hljs); + const IDENT_RE = ECMAScript.IDENT_RE; - const NAMESPACE = { - beginKeywords: 'namespace', end: /\{/, excludeEnd: true - }; - const INTERFACE = { - beginKeywords: 'interface', end: /\{/, excludeEnd: true, - keywords: 'interface extends' - }; - const USE_STRICT = { - className: 'meta', - relevance: 10, - begin: /^\s*['"]use strict['"]/ - }; const TYPES = [ "any", "void", @@ -35,6 +25,31 @@ export default function(hljs) { "never", "enum" ]; + const NAMESPACE = { + beginKeywords: 'namespace', + end: /\{/, + excludeEnd: true, + contains: [ + tsLanguage.exports.CLASS_REFERENCE + ] + }; + const INTERFACE = { + beginKeywords: 'interface', + end: /\{/, + excludeEnd: true, + keywords: { + keyword: 'interface extends', + built_in: TYPES + }, + contains: [ + tsLanguage.exports.CLASS_REFERENCE + ] + }; + const USE_STRICT = { + className: 'meta', + relevance: 10, + begin: /^\s*['"]use strict['"]/ + }; const TS_SPECIFIC_KEYWORDS = [ "type", "namespace", @@ -67,7 +82,6 @@ export default function(hljs) { mode.contains.splice(indx, 1, replacement); }; - const tsLanguage = javascript(hljs); // this should update anywhere keywords is used since // it will be the same actual JS object diff --git a/test/markup/javascript/jsx.expect.txt b/test/markup/javascript/jsx.expect.txt index 41e17403a3..c10ef1add8 100644 --- a/test/markup/javascript/jsx.expect.txt +++ b/test/markup/javascript/jsx.expect.txt @@ -25,3 +25,21 @@ } var x = 5; + +// this is NOT JSX and should not trigger the rule +interface Prefixer<Something extends string> { + (): `other__${Something}`; + + parse: <From extends string>( + value: From + ) => number; +} + +const cloneWith = <T, A extends keyof T, V>( + i: T, + a: A, + value: V +): Omit<T, A> & {[K in A]: V} => ({ + ...i, + [a]: value, +}); \ No newline at end of file diff --git a/test/markup/javascript/jsx.txt b/test/markup/javascript/jsx.txt index a76ef8eecd..4cb3104d10 100644 --- a/test/markup/javascript/jsx.txt +++ b/test/markup/javascript/jsx.txt @@ -25,3 +25,21 @@ class App extends Component { } var x = 5; + +// this is NOT JSX and should not trigger the rule +interface Prefixer { + (): `other__${Something}`; + + parse: ( + value: From + ) => number; +} + +const cloneWith = ( + i: T, + a: A, + value: V +): Omit & {[K in A]: V} => ({ + ...i, + [a]: value, +}); \ No newline at end of file