diff --git a/packages/babel-code-frame/package.json b/packages/babel-code-frame/package.json index 3f376522ab..6b217b4f94 100644 --- a/packages/babel-code-frame/package.json +++ b/packages/babel-code-frame/package.json @@ -8,11 +8,10 @@ "repository": "https://github.com/babel/babel/tree/master/packages/babel-code-frame", "main": "lib/index.js", "dependencies": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "@babel/highlight": "7.0.0-beta.39" }, "devDependencies": { + "chalk": "^2.0.0", "strip-ansi": "^4.0.0" } } diff --git a/packages/babel-code-frame/src/index.js b/packages/babel-code-frame/src/index.js index 17924b5692..f47ac52f2a 100644 --- a/packages/babel-code-frame/src/index.js +++ b/packages/babel-code-frame/src/index.js @@ -1,6 +1,4 @@ -import jsTokens, { matchToToken } from "js-tokens"; -import esutils from "esutils"; -import Chalk from "chalk"; +import highlight, { shouldHighlight, getChalk } from "@babel/highlight"; let deprecationWarningShown = false; @@ -15,21 +13,10 @@ type NodeLocation = { }; /** - * Chalk styles for token types. + * Chalk styles for code frame token types. */ - function getDefs(chalk) { return { - keyword: chalk.cyan, - capitalized: chalk.yellow, - jsx_tag: chalk.yellow, - punctuator: chalk.yellow, - // bracket: intentionally omitted. - number: chalk.magenta, - string: chalk.green, - regex: chalk.magenta, - comment: chalk.grey, - invalid: chalk.white.bgRed.bold, gutter: chalk.grey, marker: chalk.red.bold, message: chalk.red.bold, @@ -42,76 +29,6 @@ function getDefs(chalk) { const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; -/** - * RegExp to test for what seems to be a JSX tag name. - */ - -const JSX_TAG = /^[a-z][\w-]*$/i; - -/** - * RegExp to test for the three types of brackets. - */ - -const BRACKET = /^[()[\]{}]$/; - -/** - * Get the type of token, specifying punctuator type. - */ - -function getTokenType(match) { - const [offset, text] = match.slice(-2); - const token = matchToToken(match); - - if (token.type === "name") { - if (esutils.keyword.isReservedWordES6(token.value)) { - return "keyword"; - } - - if ( - JSX_TAG.test(token.value) && - (text[offset - 1] === "<" || text.substr(offset - 2, 2) == " colorize(str)) - .join("\n"); - } else { - return args[0]; - } - }); -} - /** * Extract what lines should be marked and highlighted. */ @@ -186,16 +103,13 @@ export function codeFrameColumns( opts: Object = {}, ): string { const highlighted = - (opts.highlightCode && Chalk.supportsColor) || opts.forceColor; - let chalk = Chalk; - if (opts.forceColor) { - chalk = new Chalk.constructor({ enabled: true, level: 1 }); - } + (opts.highlightCode || opts.forceColor) && shouldHighlight(opts); + const chalk = getChalk(opts); + const defs = getDefs(chalk); const maybeHighlight = (chalkFn, string) => { return highlighted ? chalkFn(string) : string; }; - const defs = getDefs(chalk); - if (highlighted) rawLines = highlight(defs, rawLines); + if (highlighted) rawLines = highlight(rawLines, opts); const lines = rawLines.split(NEWLINE); const { start, end, markerLines } = getMarkerLines(loc, lines, opts); diff --git a/packages/babel-highlight/.npmignore b/packages/babel-highlight/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-highlight/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-highlight/README.md b/packages/babel-highlight/README.md new file mode 100644 index 0000000000..36143844eb --- /dev/null +++ b/packages/babel-highlight/README.md @@ -0,0 +1,41 @@ +# @babel/highlight + +> Syntax highlight JavaScript strings for output in terminals. + +## Install + +```sh +npm install --save @babel/highlight +``` + +## Usage + +```js +import highlight from "@babel/highlight"; + +const code = `class Foo { + constructor() +}`; + +const result = highlight(code); + +console.log(result); +``` + +```js +class Foo { + constructor() +} +``` + +By default, `highlight` will not highlight your code if your terminal does not support color. To force colors, pass `{ forceColor: true }` as the second argument to `highlight`. + +```js +import highlight from "@babel/highlight"; + +const code = `class Foo { + constructor() +}`; + +const result = highlight(code, { forceColor: true }); +``` diff --git a/packages/babel-highlight/package.json b/packages/babel-highlight/package.json new file mode 100644 index 0000000000..f40486e936 --- /dev/null +++ b/packages/babel-highlight/package.json @@ -0,0 +1,18 @@ +{ + "name": "@babel/highlight", + "version": "7.0.0-beta.39", + "description": "Syntax highlight JavaScript strings for output in terminals.", + "author": "suchipi ", + "homepage": "https://babeljs.io/", + "license": "MIT", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-highlight", + "main": "lib/index.js", + "dependencies": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + }, + "devDependencies": { + "strip-ansi": "^4.0.0" + } +} diff --git a/packages/babel-highlight/src/index.js b/packages/babel-highlight/src/index.js new file mode 100644 index 0000000000..cc98a49f85 --- /dev/null +++ b/packages/babel-highlight/src/index.js @@ -0,0 +1,127 @@ +import jsTokens, { matchToToken } from "js-tokens"; +import esutils from "esutils"; +import Chalk from "chalk"; + +/** + * Chalk styles for token types. + */ +function getDefs(chalk) { + return { + keyword: chalk.cyan, + capitalized: chalk.yellow, + jsx_tag: chalk.yellow, + punctuator: chalk.yellow, + // bracket: intentionally omitted. + number: chalk.magenta, + string: chalk.green, + regex: chalk.magenta, + comment: chalk.grey, + invalid: chalk.white.bgRed.bold, + }; +} + +/** + * RegExp to test for newlines in terminal. + */ +const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; + +/** + * RegExp to test for what seems to be a JSX tag name. + */ +const JSX_TAG = /^[a-z][\w-]*$/i; + +/** + * RegExp to test for the three types of brackets. + */ +const BRACKET = /^[()[\]{}]$/; + +/** + * Get the type of token, specifying punctuator type. + */ +function getTokenType(match) { + const [offset, text] = match.slice(-2); + const token = matchToToken(match); + + if (token.type === "name") { + if (esutils.keyword.isReservedWordES6(token.value)) { + return "keyword"; + } + + if ( + JSX_TAG.test(token.value) && + (text[offset - 1] === "<" || text.substr(offset - 2, 2) == " colorize(str)) + .join("\n"); + } else { + return args[0]; + } + }); +} + +type Options = { + forceColor?: boolean, +}; + +/** + * Whether the code should be highlighted given the passed options. + */ +export function shouldHighlight(options: Options): boolean { + return Chalk.supportsColor || options.forceColor; +} + +/** + * The Chalk instance that should be used given the passed options. + */ +export function getChalk(options: Options) { + let chalk = Chalk; + if (options.forceColor) { + chalk = new Chalk.constructor({ enabled: true, level: 1 }); + } + return chalk; +} + +/** + * Highlight `code`. + */ +export default function highlight(code: string, options: Options = {}): string { + if (shouldHighlight(options)) { + const chalk = getChalk(options); + const defs = getDefs(chalk); + return highlightTokens(defs, code); + } else { + return code; + } +} diff --git a/packages/babel-highlight/test/index.js b/packages/babel-highlight/test/index.js new file mode 100644 index 0000000000..774fbcd669 --- /dev/null +++ b/packages/babel-highlight/test/index.js @@ -0,0 +1,118 @@ +import assert from "assert"; +import chalk from "chalk"; +import stripAnsi from "strip-ansi"; +import highlight, { shouldHighlight, getChalk } from ".."; + +describe("@babel/highlight", function() { + function stubColorSupport(supported) { + let originalSupportsColor; + beforeEach(function() { + originalSupportsColor = chalk.supportsColor; + chalk.supportsColor = supported; + }); + + afterEach(function() { + chalk.supportsColor = originalSupportsColor; + }); + } + + describe("highlight", function() { + describe("when colors are supported", function() { + stubColorSupport(true); + + it("highlights code", function() { + const code = "console.log('hi')"; + const result = highlight(code); + const stripped = stripAnsi(result); + assert.ok(result.length > stripped.length); + assert.equal(stripped, code); + }); + }); + + describe("when colors are not supported", function() { + stubColorSupport(false); + + it("does not attempt to highlight code", function() { + const code = "console.log('hi')"; + const result = highlight(code); + const stripped = stripAnsi(result); + assert.ok(result.length === stripped.length); + assert.equal(result, code); + }); + + describe("and the forceColor option is passed", function() { + it("highlights the code anyway", function() { + const code = "console.log('hi')"; + const result = highlight(code, { forceColor: true }); + const stripped = stripAnsi(result); + assert.ok(result.length > stripped.length); + assert.equal(stripped, code); + }); + }); + }); + }); + + describe("shouldHighlight", function() { + describe("when colors are supported", function() { + stubColorSupport(true); + + it("returns true", function() { + assert.ok(shouldHighlight({})); + }); + }); + + describe("when colors are not supported", function() { + stubColorSupport(false); + + it("returns false", function() { + assert.ok(!shouldHighlight({})); + }); + + describe("and the forceColor option is passed", function() { + it("returns true", function() { + assert.ok(shouldHighlight({ forceColor: true })); + }); + }); + }); + }); + + describe("getChalk", function() { + describe("when colors are supported", function() { + stubColorSupport(true); + + describe("when forceColor is not passed", function() { + it("returns a Chalk instance", function() { + assert.equal(getChalk({}).constructor, chalk.constructor); + }); + }); + + describe("when forceColor is passed", function() { + it("returns a Chalk instance", function() { + assert.equal( + getChalk({ forceColor: true }).constructor, + chalk.constructor, + ); + }); + }); + }); + + describe("when colors are supported", function() { + stubColorSupport(true); + + describe("when forceColor is not passed", function() { + it("returns a Chalk instance", function() { + assert.equal(getChalk({}).constructor, chalk.constructor); + }); + }); + + describe("when forceColor is passed", function() { + it("returns a Chalk instance", function() { + assert.equal( + getChalk({ forceColor: true }).constructor, + chalk.constructor, + ); + }); + }); + }); + }); +});