diff --git a/.eslintrc.js b/.eslintrc.js index b73d0a97db..930b631331 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,8 @@ module.exports = { "env": { "browser": true, "es6": true, - "node": true + "node": true, + "mocha": true }, "extends": [ "eslint:recommended", diff --git a/CHANGES.md b/CHANGES.md index d8cbfee4b4..b693cb9a4d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Deprecations: Language Improvements: +- enh(typescript/javascript/coffeescript/livescript) derive ECMAscript keywords from a common foudation (#2518) [Josh Goebel][] - enh(typescript) add setInterval, setTimeout, clearInterval, clearTimeout (#2514) [Josh Goebel][] - enh(javascript) add setInterval, setTimeout, clearInterval, clearTimeout (#2514) [Vania Kucher][] - fix(javascript) prevent `set` keyword conflicting with setTimeout, etc. (#2514) [Vania Kucher][] diff --git a/src/languages/coffeescript.js b/src/languages/coffeescript.js index 0defcd5adb..60c8bb5a4a 100644 --- a/src/languages/coffeescript.js +++ b/src/languages/coffeescript.js @@ -7,21 +7,45 @@ Category: common, scripting Website: https://coffeescript.org */ +import * as ECMAScript from "./lib/ecmascript"; + export default function(hljs) { + var COFFEE_BUILT_INS = [ + 'npm', + 'print' + ]; + var COFFEE_LITERALS = [ + 'yes', + 'no', + 'on', + 'off' + ]; + var COFFEE_KEYWORDS = [ + 'then', + 'unless', + 'until', + 'loop', + 'by', + 'when', + 'and', + 'or', + 'is', + 'isnt', + 'not' + ]; + var NOT_VALID_KEYWORDS = [ + "var", + "const", + "let", + "function", + "static" + ]; + var excluding = (list) => + (kw) => !list.includes(kw); var KEYWORDS = { - keyword: - // JS keywords - 'in if for while finally new do return else break catch instanceof throw try this ' + - 'switch continue typeof delete debugger super yield import export from as default await ' + - // Coffee keywords - 'then unless until loop of by when and or is isnt not', - literal: - // JS literals - 'true false null undefined ' + - // Coffee literals - 'yes no on off', - built_in: - 'npm require console print module global window document' + keyword: ECMAScript.KEYWORDS.concat(COFFEE_KEYWORDS).filter(excluding(NOT_VALID_KEYWORDS)).join(" "), + literal: ECMAScript.LITERALS.concat(COFFEE_LITERALS).join(" "), + built_in: ECMAScript.BUILT_INS.concat(COFFEE_BUILT_INS).join(" ") }; var JS_IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; var SUBST = { diff --git a/src/languages/javascript.js b/src/languages/javascript.js index eb0d62adc0..432863359e 100644 --- a/src/languages/javascript.js +++ b/src/languages/javascript.js @@ -5,6 +5,8 @@ Category: common, scripting Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript */ +import * as ECMAScript from "./lib/ecmascript"; + export default function(hljs) { var FRAGMENT = { begin: '<>', @@ -16,24 +18,9 @@ export default function(hljs) { }; var IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; var KEYWORDS = { - keyword: - 'in of if for while finally var new function do return void else break catch ' + - 'instanceof with throw case default try this switch continue typeof delete ' + - 'let yield const export super debugger as async await static ' + - // ECMAScript 6 modules import - 'import from as' - , - literal: - 'true false null undefined NaN Infinity', - built_in: - 'eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent ' + - 'encodeURI encodeURIComponent escape unescape Object Function Boolean Error ' + - 'EvalError InternalError RangeError ReferenceError StopIteration SyntaxError ' + - 'TypeError URIError Number Math Date String RegExp Array Float32Array ' + - 'Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array ' + - 'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' + - 'module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect ' + - 'Promise setInterval setTimeout clearInterval clearTimeout' + keyword: ECMAScript.KEYWORDS.join(" "), + literal: ECMAScript.LITERALS.join(" "), + built_in: ECMAScript.BUILT_INS.join(" ") }; var NUMBER = { className: 'number', diff --git a/src/languages/lib/ecmascript.js b/src/languages/lib/ecmascript.js new file mode 100644 index 0000000000..0e80f2c40c --- /dev/null +++ b/src/languages/lib/ecmascript.js @@ -0,0 +1,138 @@ +const KEYWORDS = [ + "as", // for exports + "in", + "of", + "if", + "for", + "while", + "finally", + "var", + "new", + "function", + "do", + "return", + "void", + "else", + "break", + "catch", + "instanceof", + "with", + "throw", + "case", + "default", + "try", + "switch", + "continue", + "typeof", + "delete", + "let", + "yield", + "const", + "class", + // JS handles these with a special rule + // "get", + // "set", + "debugger", + "async", + "await", + "static", + "import", + "from", + "export", + "extends" +]; +const LITERALS = [ + "true", + "false", + "null", + "undefined", + "NaN", + "Infinity" +]; + +const TYPES = [ + "Intl", + "DataView", + "Number", + "Math", + "Date", + "String", + "RegExp", + "Object", + "Function", + "Boolean", + "Error", + "Symbol", + "Set", + "Map", + "WeakSet", + "WeakMap", + "Proxy", + "Reflect", + "JSON", + "Promise", + "Float64Array", + "Int16Array", + "Int32Array", + "Int8Array", + "Uint16Array", + "Uint32Array", + "Float32Array", + "Array", + "Uint8Array", + "Uint8ClampedArray", + "ArrayBuffer" +]; + +const ERROR_TYPES = [ + "EvalError", + "InternalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError" +]; + +const BUILT_IN_GLOBALS = [ + "setInterval", + "setTimeout", + "clearInterval", + "clearTimeout", + + "require", + "exports", + + "eval", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "escape", + "unescape" +]; + +const BUILT_IN_VARIABLES = [ + "arguments", + "this", + "super", + "console", + "window", + "document", + "localStorage", + "module", + "global" // Node.js +]; + +const BUILT_INS = [].concat( + BUILT_IN_GLOBALS, + BUILT_IN_VARIABLES, + TYPES, + ERROR_TYPES +); + +export { LITERALS, BUILT_INS, KEYWORDS }; diff --git a/src/languages/livescript.js b/src/languages/livescript.js index 17e6ab057d..4f1f072a90 100644 --- a/src/languages/livescript.js +++ b/src/languages/livescript.js @@ -8,23 +8,57 @@ Website: https://livescript.net Category: scripting */ +import * as ECMAScript from "./lib/ecmascript"; + export default function(hljs) { + var LIVESCRIPT_BUILT_INS = [ + 'npm', + 'print' + ]; + var LIVESCRIPT_LITERALS = [ + 'yes', + 'no', + 'on', + 'off', + 'it', + 'that', + 'void' + ]; + var LIVESCRIPT_KEYWORDS = [ + 'then', + 'unless', + 'until', + 'loop', + 'of', + 'by', + 'when', + 'and', + 'or', + 'is', + 'isnt', + 'not', + 'it', + 'that', + 'otherwise', + 'from', + 'to', + 'til', + 'fallthrough', + 'case', + 'enum', + 'native', + 'list', + 'map', + '__hasProp', + '__extends', + '__slice', + '__bind', + '__indexOf' + ]; var KEYWORDS = { - keyword: - // JS keywords - 'in if for while finally new do return else break catch instanceof throw try this ' + - 'switch continue typeof delete debugger case default function var with ' + - // LiveScript keywords - 'then unless until loop of by when and or is isnt not it that otherwise from to til fallthrough super ' + - 'case default function var void const let enum export import native list map ' + - '__hasProp __extends __slice __bind __indexOf', - literal: - // JS literals - 'true false null undefined ' + - // LiveScript literals - 'yes no on off it that void', - built_in: - 'npm require console print module global window document' + keyword: ECMAScript.KEYWORDS.concat(LIVESCRIPT_KEYWORDS).join(" "), + literal: ECMAScript.LITERALS.concat(LIVESCRIPT_LITERALS).join(" "), + built_in: ECMAScript.BUILT_INS.concat(LIVESCRIPT_BUILT_INS).join(" ") }; var JS_IDENT_RE = '[A-Za-z$_](?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*'; var TITLE = hljs.inherit(hljs.TITLE_MODE, {begin: JS_IDENT_RE}); diff --git a/src/languages/typescript.js b/src/languages/typescript.js index fd7f59ad7f..adfed257d3 100644 --- a/src/languages/typescript.js +++ b/src/languages/typescript.js @@ -7,26 +7,36 @@ Website: https://www.typescriptlang.org Category: common, scripting */ +import * as ECMAScript from "./lib/ecmascript"; + export default function(hljs) { var JS_IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; + var TYPES = [ + "any", + "void", + "number", + "boolean", + "string", + "object", + "never", + "enum" + ]; + var TS_SPECIFIC_KEYWORDS = [ + "type", + "namespace", + "typedef", + "interface", + "public", + "private", + "protected", + "implements", + "declare", + "abstract" + ]; var KEYWORDS = { - keyword: - 'in if for while finally var new function do return void else break catch ' + - 'instanceof with throw case default try this switch continue typeof delete ' + - 'let yield const class public private protected get set super ' + - 'static implements enum export import declare type namespace abstract ' + - 'as from extends async await', - literal: - 'true false null undefined NaN Infinity', - built_in: - 'eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent ' + - 'encodeURI encodeURIComponent escape unescape Object Function Boolean Error ' + - 'EvalError InternalError RangeError ReferenceError StopIteration SyntaxError ' + - 'TypeError URIError Number Math Date String RegExp Array Float32Array ' + - 'Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array ' + - 'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' + - 'module console window document any number boolean string void Promise ' + - 'setInterval setTimeout clearInterval clearTimeout' + keyword: ECMAScript.KEYWORDS.concat(TS_SPECIFIC_KEYWORDS).join(" "), + literal: ECMAScript.LITERALS.join(" "), + built_in: ECMAScript.BUILT_INS.concat(TYPES).join(" ") }; var DECORATOR = { className: 'meta', diff --git a/test/detect/index.js b/test/detect/index.js index fec7bb3276..b6daf0f31e 100644 --- a/test/detect/index.js +++ b/test/detect/index.js @@ -1,46 +1,44 @@ 'use strict'; -delete require.cache[require.resolve('../../build')] -delete require.cache[require.resolve('../../build/lib/core')] +delete require.cache[require.resolve('../../build')]; +delete require.cache[require.resolve('../../build/lib/core')]; -const fs = require('fs').promises; -const hljs = require('../../build'); +const fs = require('fs').promises; +const hljs = require('../../build'); hljs.debugMode(); // tests run in debug mode so errors are raised -const path = require('path'); -const utility = require('../utility'); -const { getThirdPartyPackages } = require('../../tools/lib/external_language') +const path = require('path'); +const utility = require('../utility'); +const { getThirdPartyPackages } = require('../../tools/lib/external_language'); -function testAutoDetection(language, {detectPath}) { +function testAutoDetection(language, { detectPath }) { const languagePath = detectPath || utility.buildPath('detect', language); - it(`should be detected as ${language}`, async () => { + it(`should be detected as ${language}`, async() => { const dir = await fs.stat(languagePath); - dir.isDirectory().should.be.true; + dir.isDirectory().should.be.true(); - const filenames = await fs.readdir(languagePath) - const filesContent = await Promise.all(filenames - .map(function(example) { + const filenames = await fs.readdir(languagePath); + await Promise.all(filenames + .map(async function(example) { const filename = path.join(languagePath, example); - return fs.readFile(filename, 'utf-8'); - })) - filesContent.forEach(function(content) { - const expected = language, - actual = hljs.highlightAuto(content).language; + const content = await fs.readFile(filename, 'utf-8'); + const detectedLanguage = hljs.highlightAuto(content).language; - actual.should.equal(expected); - }); + detectedLanguage.should.equal(language, + `${path.basename(filename)} should be detected as ${language}, but was ${detectedLanguage}`); + })); }); } describe('hljs.highlightAuto()', () => { - before( async function() { - let thirdPartyPackages = await getThirdPartyPackages(); + before(async function() { + const thirdPartyPackages = await getThirdPartyPackages(); - let languages = hljs.listLanguages(); + const languages = hljs.listLanguages(); describe(`hljs.highlightAuto()`, function() { languages.filter(hljs.autoDetection).forEach((language) => { - let detectPath = detectTestDir(language); + const detectPath = detectTestDir(language); testAutoDetection(language, { detectPath }); }); }); @@ -50,13 +48,11 @@ describe('hljs.highlightAuto()', () => { for (let i = 0; i < thirdPartyPackages.length; ++i) { const pkg = thirdPartyPackages[i]; const idx = pkg.names.indexOf(name); - if (idx !== -1) - return pkg.detectTestPaths[idx] + if (idx !== -1) return pkg.detectTestPaths[idx]; } return null; // test not found } }); - it("adding dynamic tests...", async function() {} ); // this is required to work + it("adding dynamic tests...", async function() {}); // this is required to work }); - diff --git a/test/markup/coffeescript/function.expect.txt b/test/markup/coffeescript/function.expect.txt index 0c4f8af86f..4d43e641b3 100644 --- a/test/markup/coffeescript/function.expect.txt +++ b/test/markup/coffeescript/function.expect.txt @@ -3,12 +3,12 @@ square = (x) -> x * x npmWishlist.sha256 = (str) -> - throw new Error() + throw new Error() str.split(" ").map((m) -> m.charCodeAt(0)) fs.readFile("package.json", "utf-8", (err, content) -> - data = JSON.parse(content) + data = JSON.parse(content) data.version ) diff --git a/test/markup/javascript/class.expect.txt b/test/markup/javascript/class.expect.txt index 31a8507e4c..c631c8f819 100644 --- a/test/markup/javascript/class.expect.txt +++ b/test/markup/javascript/class.expect.txt @@ -1,11 +1,11 @@ class Car extends Vehicle { constructor(speed, cost) { - super(speed); + super(speed); var c = Symbol('cost'); - this[c] = cost; + this[c] = cost; - this.intro = `This is a car runs at + this.intro = `This is a car runs at ${speed}.`; } } diff --git a/test/markup/typescript/class.expect.txt b/test/markup/typescript/class.expect.txt index ff99191bf4..41894432d7 100644 --- a/test/markup/typescript/class.expect.txt +++ b/test/markup/typescript/class.expect.txt @@ -1,11 +1,11 @@ class Car extends Vehicle { constructor(speed, cost) { - super(speed); + super(speed); - var c = Symbol('cost'); - this[c] = cost; + var c = Symbol('cost'); + this[c] = cost; - this.intro = `This is a car runs at + this.intro = `This is a car runs at ${speed}.`; } }