[Pkg-javascript-commits] [node-tippex] 01/02: New upstream version 3.0.0+ds

Julien Puydt julien.puydt at laposte.net
Wed Feb 28 10:37:42 UTC 2018


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository node-tippex.

commit 7c79f3887f7b04c13853257ea564f3db656cbb20
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Wed Feb 28 10:26:01 2018 +0100

    New upstream version 3.0.0+ds
---
 .eslintignore                       |   1 +
 .eslintrc                           |  24 +++
 .gitignore                          |   3 +
 .travis.yml                         |   9 +
 CHANGELOG.md                        |  51 +++++
 README.md                           | 128 +++++++++++
 package.json                        |  40 ++++
 rollup.config.js                    |  18 ++
 src/index.js                        | 416 ++++++++++++++++++++++++++++++++++++
 test/samples/imports.js             |   5 +
 test/samples/jsxAfter.jsx           |  14 ++
 test/samples/jsxBefore.jsx          |  14 ++
 test/samples/misc.js                |  33 +++
 test/samples/regexDivisionAfter.js  |  20 ++
 test/samples/regexDivisionBefore.js |  20 ++
 test/samples/requires.js            |   5 +
 test/test.js                        | 306 ++++++++++++++++++++++++++
 17 files changed, 1107 insertions(+)

diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..2c08342
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+test/samples/**/*.js
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..e7d1551
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,24 @@
+{
+    "rules": {
+        "indent": [ 2, "tab", { "SwitchCase": 1 } ],
+        "quotes": [ 0 ],
+        "linebreak-style": [ 2, "unix" ],
+        "semi": [ 2, "always" ],
+        "keyword-spacing": [ 2, {"before": true, "after": true}],
+        "space-before-blocks": [ 2, "always" ],
+        "space-before-function-paren": [ 2, "always" ],
+        "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
+        "no-cond-assign": [ 0 ]
+    },
+    "env": {
+        "es6": true,
+        "browser": true,
+        "mocha": true,
+        "node": true
+    },
+    "extends": "eslint:recommended",
+    "parserOptions": {
+        "ecmaVersion": 6,
+        "sourceType": "module"
+    }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8d67a86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+node_modules
+dist
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..646cc3d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+sudo: false
+language: node_js
+node_js:
+  - "4"
+  - "stable"
+env:
+  global:
+    - BUILD_TIMEOUT=10000
+install: npm install
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4e3f5c2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,51 @@
+# Tippex changelog
+
+## 3.0.0
+
+* Massive performance improvements, especially with large files ([#17](https://github.com/Rich-Harris/tippex/issues/17))
+* Found tokens now take the form `{ start, end, type, value }` — no more `inner` and `outer`
+* Text content inside JSX tags is removed
+* Fix bug with keyword false positives ([#18](https://github.com/Rich-Harris/tippex/issues/18))
+
+## 2.3.1
+
+* Handle escaped slash at start of regex ([#15](https://github.com/Rich-Harris/tippex/issues/15))
+
+## 2.3.0
+
+* JSX support ([#14](https://github.com/Rich-Harris/tippex/pull/14))
+
+## 2.2.0
+
+* Include `default` among keywords that signal `/` should be treated as start of regex literal ([#1](https://github.com/Rich-Harris/tippex/issues/1))
+* More informative error message than 'state is not a function'
+
+## 2.1.2
+
+* Fix crash on double asterisk in closing block comment ([#10](https://github.com/Rich-Harris/tippex/pull/10))
+
+## 2.1.1
+
+* Handle `$` in template literals ([#1](https://github.com/Rich-Harris/tippex/issues/1))
+* Fix crash on specific comments ([#8](https://github.com/Rich-Harris/tippex/issues/8))
+
+
+## 2.1.0
+
+* Handle prefix `++`/`--` operators followed by `/` ([#5](https://github.com/Rich-Harris/tippex/pull/5))
+
+## 2.0.0
+
+* Handle majority (see note in README) of characters that could be either a division operator or the start of a regular expression literal ([#3](https://github.com/Rich-Harris/tippex/pull/3))
+
+## 1.2.0
+
+* Add `tippex.replace` method
+
+## 1.1.0
+
+* Add `tippex.match` method
+
+## 1.0.0
+
+* First release
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4ef1fc6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,128 @@
+# Tippex
+
+Erase comments, strings and regular expressions from JavaScript code.
+
+## Why?
+
+Say you want to do some very simple code analysis, such as finding `import` and `export` statements. You *could* just skim over the code with a regex, but you'll get bad results if matches exist inside comments or strings:
+
+```js
+import a from './a.js';
+// import b from './b.js'; TODO do we need this?
+```
+
+Instead, you might generate an abstract syntax tree with a parser like [Acorn](https://github.com/ternjs/acorn), and traverse the AST looking for nodes of a specific type. But for a lot of simple tasks that's overkill – parsing is expensive, traversing is a lot less simple than using regular expressions, and if you're doing anything in the browser it's better to avoid large dependencies.
+
+Tippex offers some middle ground. It's as robust as a full-fledged parser, but miniscule – and much faster. (Americans: Tippex is what you oddballs call 'Liquid Paper' or 'Wite-Out'.)
+
+
+## What does it do?
+
+Tippex simply replaces the contents of strings (including ES6 template strings), regular expressions and comments with the equivalent whitespace.
+
+So this...
+
+```js
+var a = 1; // line comment
+/*
+  block comment
+*/
+var b = 2;
+var c = /\w+/;
+var d = 'some text';
+var e = "some more text";
+var f = `an ${ 'unnecessarily' ? `${'complicated'}` : `${'template'}` } string`;
+```
+
+...becomes this:
+
+```js
+var a = 1; //
+/*
+
+*/
+var b = 2;
+var c = /   /;
+var d = '         ';
+var e = "              ";
+var f = `   ${ '             ' ? `${'           '}` : `${'        '}` }       `;
+```
+
+Once that's done, you can search for patterns (such as `var` or ` = ` or `import`) in complete confidence that you won't get any false positives.
+
+
+## Installation
+
+```bash
+npm install --save tippex
+```
+
+...or download from unpkg.com ([UMD version](https://unpkg.com/tippex), [ES6 exports version](https://unpkg.com/tippex/dist/tippex.es.js)).
+
+
+## Usage
+
+```js
+import * as tippex from 'tippex'; // or `var tippex = require('tippex')`, etc
+
+var erased = tippex.erase( 'var a = 1; // line comment' );
+// -> 'var a = 1; //             '
+
+var found = tippex.find( 'var a = 1; // line comment' );
+// -> [{
+//      start: 11,
+//      end: 26,
+//      type: 'line',
+//      outer: '// line comment',
+//      inner: ' line comment'
+//    }]
+```
+
+Sometimes you might need to match a regular expression against the original string, but ignoring comments etc. For that you can use `tippex.match`:
+
+```js
+var code = `
+import a from './a.js';
+// import b from './b.js'; TODO do we need this?
+`;
+
+var importPattern = /import (.+?) from '([^']+)'/g; // must have 'g' flag
+var importDeclarations = [];
+
+tippex.match( code, importPattern, ( match, name, source ) => {
+  // this callback will be called for each match that *doesn't* begin
+  // inside a comment, string or regular expression
+  importDeclarations.push({ name, source });
+});
+
+console.log( importDeclarations );
+// -> [{
+//       name: 'a',
+//       source: './a.js'
+//    }]
+```
+
+(A complete regular expression for ES6 imports would be a bit more complicated; this is for illustrative purposes.)
+
+To replace occurrences of a pattern that aren't inside strings or comments, use `tippex.replace`:
+
+```js
+code = tippex.replace( code, importPattern, ( match, name, source ) => {
+  return `var ${name} = require('${source}')`;
+});
+```
+
+
+## Known issues
+
+It's extremely difficult to distinguish between regular expression literals and division operators in certain edge cases at the lexical level. Fortunately, these cases are rare and generally somewhat contrived. If you encounter one in the wild, please raise an issue so we can try to accommodate it.
+
+
+
+## License
+
+MIT
+
+----
+
+Follow [@Rich_Harris](https://twitter.com/Rich_Harris) on Twitter for more artisanal, hand-crafted JavaScript.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..daaf35a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,40 @@
+{
+  "name": "tippex",
+  "description": "Find and erase strings and comments in JavaScript code",
+  "version": "3.0.0",
+  "author": "Rich Harris",
+  "main": "dist/tippex.umd.js",
+  "module": "dist/tippex.es.js",
+  "files": [
+    "src",
+    "dist",
+    "README.md"
+  ],
+  "license": "MIT",
+  "repository": "https://github.com/Rich-Harris/tippex",
+  "scripts": {
+    "bench": "node bench",
+    "test": "mocha --compilers js:buble/register",
+    "prebench": "npm run build",
+    "pretest": "npm run build",
+    "build": "rollup -c",
+    "prepublish": "npm run lint && rm -rf dist && npm test",
+    "lint": "eslint src"
+  },
+  "devDependencies": {
+    "acorn": "^4.0.11",
+    "benchmark": "^2.1.3",
+    "buble": "^0.15.2",
+    "console-group": "^0.3.3",
+    "eslint": "^3.17.1",
+    "locate-character": "^2.0.0",
+    "glob": "^7.1.1",
+    "mocha": "^3.2.0",
+    "pretty-bytes": "^4.0.2",
+    "pretty-ms": "^2.1.0",
+    "rollup": "^0.41.5",
+    "rollup-plugin-buble": "^0.15.0",
+    "rollup-plugin-node-resolve": "^2.0.0",
+    "source-map-support": "^0.4.12"
+  }
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..78dca27
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,18 @@
+import buble from 'rollup-plugin-buble';
+import nodeResolve from 'rollup-plugin-node-resolve';
+
+const pkg = require( './package.json' );
+
+export default {
+	entry: 'src/index.js',
+	plugins: [
+		nodeResolve(),
+		buble({ exclude: 'node_modules/**' })
+	],
+	moduleName: 'tippex',
+	sourceMap: true,
+	targets: [
+		{ dest: pkg.main, format: 'umd' },
+		{ dest: pkg.module, format: 'es' }
+	]
+};
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..095c33f
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,416 @@
+import { locate } from 'locate-character';
+
+const keywords = /\b(case|default|delete|do|else|in|instanceof|new|return|throw|typeof|void)\s*$/;
+const punctuators = /(^|\{|\(|\[\.|;|,|<|>|<=|>=|==|!=|===|!==|\+|-|\*\%|<<|>>|>>>|&|\||\^|!|~|&&|\|\||\?|:|=|\+=|-=|\*=|%=|<<=|>>=|>>>=|&=|\|=|\^=|\/=|\/)\s*$/;
+const ambiguous = /(\}|\)|\+\+|--)\s*$/;
+
+const punctuatorChars = /[{}()[.;,<>=+\-*%&|\^!~?:/]/;
+const keywordChars = /[a-z]/;
+const beforeJsxChars = /[=:;,({}[&+]/;
+
+const whitespace = /\s/;
+
+function isWhitespace ( char ) {
+	return whitespace.test( char );
+}
+
+function isPunctuatorChar ( char ) {
+	return punctuatorChars.test( char );
+}
+
+function isKeywordChar ( char ) {
+	return keywordChars.test( char );
+}
+
+function isPunctuator ( str ) {
+	return punctuators.test( str );
+}
+
+function isKeyword ( str ) {
+	return keywords.test( str );
+}
+
+function isAmbiguous ( str ) {
+	return ambiguous.test( str );
+}
+
+export function find ( str ) {
+	let quote;
+	let escapedFrom;
+	let regexEnabled = true;
+	let pfixOp = false;
+	let jsxTagDepth = 0;
+	let stack = [];
+
+	let start;
+	let found = [];
+	let state = base;
+
+	let lsci = -1; // last significant character index
+	const lsc = () => str[ lsci ];
+
+	const parenMatches = {};
+	const openingParenPositions = {};
+	let parenDepth = 0;
+
+	function tokenClosesExpression () {
+		if ( lsc() === ')' ) {
+			let c = parenMatches[ lsci ];
+			while ( isWhitespace( str[ c - 1 ] ) ) c -= 1;
+
+			// if parenthesized expression is immediately preceded by `if`/`while`, it's not closing an expression
+			return !/(if|while)$/.test( str.slice( c - 5, c ) );
+		}
+
+		// TODO handle }, ++ and -- tokens immediately followed by / character
+		return true;
+	}
+
+	function base ( char, i ) {
+		// the order of these tests is based on which characters are
+		// typically more prevalent in a codebase
+		if ( char === '(' ) {
+			lsci = i;
+			openingParenPositions[ parenDepth++ ] = i;
+			return base;
+		}
+
+		if ( char === ')' ) {
+			lsci = i;
+			parenMatches[i] = openingParenPositions[ --parenDepth ];
+			return base;
+		}
+
+		if ( char === '{' ) {
+			lsci = i;
+			stack.push( base );
+			return base;
+		}
+
+		if ( char === '}' ) {
+			lsci = i;
+			return start = i + 1, stack.pop();
+		}
+
+		if ( char === '"' || char === "'" ) {
+			start = i + 1;
+			quote = char;
+			stack.push( base );
+			return string;
+		}
+
+		if ( char === '/' ) {
+			// could be start of regex literal OR division punctuator. Solution via
+			// http://stackoverflow.com/questions/5519596/when-parsing-javascript-what-determines-the-meaning-of-a-slash/27120110#27120110
+
+			let b = i;
+			while ( b > 0 && isWhitespace( str[ b - 1 ] ) ) b -= 1;
+
+			if ( b > 0 ) {
+				let a = b;
+
+				if ( isPunctuatorChar( str[ a - 1 ] ) ) {
+					while ( a > 0 && isPunctuatorChar( str[ a - 1 ] ) ) a -= 1;
+				} else {
+					while ( a > 0 && isKeywordChar( str[ a - 1 ] ) ) a -= 1;
+				}
+
+				const token = str.slice( a, b );
+
+				regexEnabled = token ? (
+					isKeyword( token ) ||
+					isPunctuator( token ) ||
+					( isAmbiguous( token ) && !tokenClosesExpression() )
+				) : false;
+			} else {
+				regexEnabled = true;
+			}
+
+			start = i;
+			return slash;
+		}
+
+		if ( char === '`' ) {
+			start = i + 1;
+			return templateString;
+		}
+
+		if ( char === '<' && ( !~lsci || beforeJsxChars.test( lsc() ) ) ) {
+			stack.push( base );
+			return jsxTagStart;
+		}
+
+		if ( char === '+' && !pfixOp && str[ i - 1 ] === '+' ) {
+			pfixOp = true;
+		} else if ( char === '-' && !pfixOp && str[ i - 1 ] === '-' ) {
+			pfixOp = true;
+		}
+
+		if ( !isWhitespace( char ) ) lsci = i;
+		return base;
+	}
+
+	function slash ( char, i ) {
+		if ( char === '/' ) return start = i + 1, lineComment;
+		if ( char === '*' ) return start = i + 1, blockComment;
+		if ( char === '[' ) return regexEnabled ? ( start = i, regexCharacter ) : base;
+		if ( char === '\\' ) return start = i, escapedFrom = regex, escaped;
+		return regexEnabled && !pfixOp ? ( start = i, regex ) : base;
+	}
+
+	function regex ( char, i ) {
+		if ( char === '[' ) return regexCharacter;
+		if ( char === '\\' ) return escapedFrom = regex, escaped;
+
+		if ( char === '/' ) {
+			const end = i;
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'regex' });
+
+			return base;
+		}
+
+		return regex;
+	}
+
+	function regexCharacter ( char ) {
+		if ( char === ']' ) return regex;
+		if ( char === '\\' ) return escapedFrom = regexCharacter, escaped;
+		return regexCharacter;
+	}
+
+	function string ( char, i ) {
+		if ( char === '\\' ) return escapedFrom = string, escaped;
+		if ( char === quote ) {
+			const end = i;
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'string' });
+
+			return stack.pop();
+		}
+
+		return string;
+	}
+
+	function escaped () {
+		return escapedFrom;
+	}
+
+	function templateString ( char, i ) {
+		if ( char === '$' ) return templateStringDollar;
+		if ( char === '\\' ) return escapedFrom = templateString, escaped;
+
+		if ( char === '`' ) {
+			const end = i;
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'templateEnd' });
+
+			return base;
+		}
+
+		return templateString;
+	}
+
+	function templateStringDollar ( char, i ) {
+		if ( char === '{' ) {
+			const end = i - 1;
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'templateChunk' });
+
+			stack.push( templateString );
+			return base;
+		}
+		return templateString( char, i );
+	}
+
+	// JSX is an XML-like extension to ECMAScript
+	// https://facebook.github.io/jsx/
+
+	function jsxTagStart ( char ) {
+		if ( char === '/' ) return jsxTagDepth--, jsxTag;
+		return jsxTagDepth++, jsxTag;
+	}
+
+	function jsxTag ( char, i ) {
+		if ( char === '"' || char === "'" ) return start = i + 1, quote = char, stack.push( jsxTag ), string;
+		if ( char === '{' ) return stack.push( jsxTag ), base;
+		if ( char === '>' ) {
+			if ( jsxTagDepth <= 0 ) return base;
+			start = i + 1;
+			return jsx;
+		}
+		if ( char === '/' ) return jsxTagSelfClosing;
+
+		return jsxTag;
+	}
+
+	function jsxTagSelfClosing ( char ) {
+		if ( char === '>' ) {
+			jsxTagDepth--;
+			if ( jsxTagDepth <= 0 ) return base;
+			return jsx;
+		}
+
+		return jsxTag;
+	}
+
+	function jsx ( char, end ) {
+		if ( char === '{' || char === '<' ) {
+			const value = str.slice( start, end );
+			found.push({ start, end, value, type: 'jsx' });
+
+			if ( char === '{' ) {
+				return stack.push( jsx ), base;
+			}
+
+			if ( char === '<' ) {
+				return jsxTagStart;
+			}
+		}
+
+		return jsx;
+	}
+
+	function lineComment ( char, end ) {
+		if ( char === '\n' ) {
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'line' });
+
+			return base;
+		}
+
+		return lineComment;
+	}
+
+	function blockComment ( char ) {
+		if ( char === '*' ) return blockCommentEnding;
+		return blockComment;
+	}
+
+	function blockCommentEnding ( char, i ) {
+		if ( char === '/' ) {
+			const end = i - 1;
+			const value = str.slice( start, end );
+
+			found.push({ start, end, value, type: 'block' });
+
+			return base;
+		}
+
+		return blockComment( char );
+	}
+
+	for ( let i = 0; i < str.length; i += 1 ) {
+		if ( !state ) {
+			const { line, column } = locate( str, i, { offsetLine: 1 });
+			const before = str.slice( 0, i );
+			const beforeLine = /(^|\n).+$/.exec( before )[0];
+			const after = str.slice( i );
+			const afterLine = /.+(\n|$)/.exec( after )[0];
+
+			const snippet = `${beforeLine}${afterLine}\n${ Array( beforeLine.length + 1 ).join( ' ' )}^`;
+
+			throw new Error( `Unexpected character (${line}:${column}). If this is valid JavaScript, it's probably a bug in tippex. Please raise an issue at https://github.com/Rich-Harris/tippex/issues – thanks!\n\n${snippet}` );
+		}
+
+		state = state( str[i], i );
+	}
+
+	// cheeky hack
+	if ( state.name === 'lineComment' ) state( '\n', str.length );
+
+	return found;
+}
+
+export function erase ( str ) {
+	const found = find( str );
+	return _erase( str, found );
+}
+
+function _erase ( str, found ) {
+	let erased = '';
+	let charIndex = 0;
+
+	for ( let i = 0; i < found.length; i += 1 ) {
+		const chunk = found[i];
+		erased += str.slice( charIndex, chunk.start );
+		erased += chunk.value.replace( /[^\n]/g, ' ' );
+
+		charIndex = chunk.end;
+	}
+
+	erased += str.slice( charIndex );
+	return erased;
+}
+
+function makeGlobalRegExp ( original ) {
+	let flags = 'g';
+
+	if ( original.multiline ) flags += 'm';
+	if ( original.ignoreCase ) flags += 'i';
+	if ( original.sticky ) flags += 'y';
+	if ( original.unicode ) flags += 'u';
+
+	return new RegExp( original.source, flags );
+}
+
+export function match ( str, pattern, callback ) {
+	const g = pattern.global;
+	if ( !g ) pattern = makeGlobalRegExp( pattern );
+
+	const found = find( str );
+
+	let match;
+	let chunkIndex = 0;
+
+	while ( match = pattern.exec( str ) ) {
+		let chunk;
+
+		do {
+			chunk = found[ chunkIndex ];
+
+			if ( chunk && chunk.end < match.index ) {
+				chunkIndex += 1;
+			} else {
+				break;
+			}
+		} while ( chunk );
+
+		if ( !chunk || chunk.start > match.index ) {
+			const args = [].slice.call( match ).concat( match.index, str );
+			callback.apply( null, args );
+			if ( !g ) break;
+		}
+	}
+}
+
+export function replace ( str, pattern, callback ) {
+	let replacements = [];
+
+	match( str, pattern, function ( match ) {
+		const start = arguments[ arguments.length - 2 ];
+		const end = start + match.length;
+		const content = callback.apply( null, arguments );
+
+		replacements.push({ start, end, content });
+	});
+
+	let replaced = '';
+	let lastIndex = 0;
+
+	for ( let i = 0; i < replacements.length; i += 1 ) {
+		const { start, end, content } = replacements[i];
+		replaced += str.slice( lastIndex, start ) + content;
+
+		lastIndex = end;
+	}
+
+	replaced += str.slice( lastIndex );
+
+	return replaced;
+}
diff --git a/test/samples/imports.js b/test/samples/imports.js
new file mode 100644
index 0000000..af872ba
--- /dev/null
+++ b/test/samples/imports.js
@@ -0,0 +1,5 @@
+import a from './a.js';
+// import b from './b.js';
+import c from './c.js';
+
+const str = "import d from './d.js';";
diff --git a/test/samples/jsxAfter.jsx b/test/samples/jsxAfter.jsx
new file mode 100644
index 0000000..dd63fdb
--- /dev/null
+++ b/test/samples/jsxAfter.jsx
@@ -0,0 +1,14 @@
+<a>    {/**/}</a>/**/;
+{ <a>    {/**/}</a>/**/ };
+/**/<a>    {/**/}</a>/**/;
+let x = <a>    {/**/}</a>/**/;
+foo(<a>    {/**/}</a>, <a>    {/**/}</a>, (<a>    {/**/}</a>))/**/;
+<a x={x/**/} y="     ">    {/**/}</a>/**/;
+/**/<a><sub x={x/**/}>    {/**/}</sub></a>/**/;
+let r = x < 10 /**/ && x > y ? 1 /**/ : 2;
+if (x < 10) { <a x={x/**/}>    {/**/}</a>/**/ }/**/;
+<a x={x/**/ > 10/**/}>    {x /**/ < 10 /**/ || x /**/ > 10 /**/}    </a>/**/;
+<a></a>; function x() { <a>    {/**/}</a>/**/ } /**/;
+<a></a>; function x() { <a>    {/**/}</a>/**/ }
+
+<foo x=" " y={/**/}/>/**/;
diff --git a/test/samples/jsxBefore.jsx b/test/samples/jsxBefore.jsx
new file mode 100644
index 0000000..6549d39
--- /dev/null
+++ b/test/samples/jsxBefore.jsx
@@ -0,0 +1,14 @@
+<a>/**/{/**/}</a>/**/;
+{ <a>/**/{/**/}</a>/**/ };
+/**/<a>/**/{/**/}</a>/**/;
+let x = <a>/**/{/**/}</a>/**/;
+foo(<a>/**/{/**/}</a>, <a>/**/{/**/}</a>, (<a>/**/{/**/}</a>))/**/;
+<a x={x/**/} y="y/**/">/**/{/**/}</a>/**/;
+/**/<a><sub x={x/**/}>/**/{/**/}</sub></a>/**/;
+let r = x < 10 /**/ && x > y ? 1 /**/ : 2;
+if (x < 10) { <a x={x/**/}>/**/{/**/}</a>/**/ }/**/;
+<a x={x/**/ > 10/**/}>/**/{x /**/ < 10 /**/ || x /**/ > 10 /**/}/**/</a>/**/;
+<a></a>; function x() { <a>/**/{/**/}</a>/**/ } /**/;
+<a></a>; function x() { <a>/**/{/**/}</a>/**/ }
+
+<foo x="x" y={/**/}/>/**/;
diff --git a/test/samples/misc.js b/test/samples/misc.js
new file mode 100644
index 0000000..bad6e6e
--- /dev/null
+++ b/test/samples/misc.js
@@ -0,0 +1,33 @@
+const answer = 42; // line comment
+const moarAnswer = (7*4)/(2/3)
+
+/*
+  (•_•)
+  <)   )╯Multi
+  /    \
+
+  \(•_•)
+  (   (> Line
+  /    \
+
+  (•_•)
+  <)   )>  Comment
+  /    \
+*/
+
+const singleQuotedString = 'i\'m trying to escape';
+const doubleQuotedString = "this is also \"escaped\"";
+const templateString = `the answer is ${answer}. This is a backtick: \``;
+
+const regex = /you can ignore\/[/]skip me, it's cool/;
+
+if ( a / b ) /foo/;
+
+function calc (data) {
+  for (i = 0; i < num; i++)
+    something = item[i] / total * 100
+
+  for (i = 0; i < 4; i++) {
+    something = Math.round(totals[i] / total * 100)
+  }
+}
diff --git a/test/samples/regexDivisionAfter.js b/test/samples/regexDivisionAfter.js
new file mode 100644
index 0000000..57f04b0
--- /dev/null
+++ b/test/samples/regexDivisionAfter.js
@@ -0,0 +1,20 @@
+/   /;
+/   /;
+a
+/b/g;
+a/b/g;
+(a)/b/g;
+(a/b)/c;
+(a/b)/c/g;
+if (a)/ /g;
+if (a/b)/ /g;
+while (a)/ /g;
+while (a/b)/ /g;
+while ((m = /      /.exec(str)) a/=2/g;
+a++/b/g;
+a++ /b/g;
+a--/b/g;
+a-- /b/g;
+(a)++/b/g;
+(a++)/b/g;
+i/=j/2;
diff --git a/test/samples/regexDivisionBefore.js b/test/samples/regexDivisionBefore.js
new file mode 100644
index 0000000..117c751
--- /dev/null
+++ b/test/samples/regexDivisionBefore.js
@@ -0,0 +1,20 @@
+/foo/;
+/foo/;
+a
+/b/g;
+a/b/g;
+(a)/b/g;
+(a/b)/c;
+(a/b)/c/g;
+if (a)/b/g;
+if (a/b)/c/g;
+while (a)/b/g;
+while (a/b)/c/g;
+while ((m = /..(.*)/.exec(str)) a/=2/g;
+a++/b/g;
+a++ /b/g;
+a--/b/g;
+a-- /b/g;
+(a)++/b/g;
+(a++)/b/g;
+i/=j/2;
diff --git a/test/samples/requires.js b/test/samples/requires.js
new file mode 100644
index 0000000..448f8f3
--- /dev/null
+++ b/test/samples/requires.js
@@ -0,0 +1,5 @@
+var a = require('./a.js');
+// import b from './b.js';
+var c = require('./c.js');
+
+const str = "import d from './d.js';";
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..3e815a9
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,306 @@
+const fs = require( 'fs' );
+const assert = require( 'assert' );
+const { describe, it } = require( 'mocha' );
+
+require( 'source-map-support' ).install();
+require( 'console-group' ).install();
+
+const tippex = require( '../' );
+
+let samples = {};
+fs.readdirSync( 'test/samples' ).forEach( file => {
+	samples[ file.replace( /\.jsx?$/, '' ) ] = fs.readFileSync( `test/samples/${file}`, 'utf-8' );
+});
+
+describe( 'tippex', () => {
+	describe( 'find', () => {
+		let found;
+
+		before( () => {
+			found = tippex.find( samples.misc );
+		});
+
+		it( 'finds line comments', () => {
+			const lines = found.filter( chunk => chunk.type === 'line' );
+
+			const start = samples.misc.indexOf( '//' ) + 2;
+			const end = samples.misc.indexOf( '\n', start );
+
+			assert.equal( lines.length, 1 );
+			assert.deepEqual( lines[0], {
+				start,
+				end,
+				value: ' line comment',
+				type: 'line'
+			});
+		});
+
+		it( 'finds block comments', () => {
+			const blocks = found.filter( chunk => chunk.type === 'block' );
+
+			const start = samples.misc.indexOf( '/*' ) + 2;
+			const end = samples.misc.indexOf( '*/' );
+
+			const comment = samples.misc.slice( start, end );
+
+			assert.equal( blocks.length, 1 );
+			assert.deepEqual( blocks[0], {
+				start,
+				end,
+				value: comment,
+				type: 'block'
+			});
+		});
+
+		it( 'finds regular expressions', () => {
+			const regexes = found.filter( chunk => chunk.type === 'regex' );
+
+			const start = samples.misc.indexOf( '/you' ) + 1;
+			const end = samples.misc.indexOf( 'cool/' ) + 4;
+			const regex = samples.misc.slice( start, end );
+
+			assert.equal( regexes.length, 2 );
+			assert.deepEqual( regexes[0], {
+				start,
+				end,
+				value: regex,
+				type: 'regex'
+			});
+		});
+
+		it( 'finds template strings', () => {
+			const templateStrings = found.filter( chunk => chunk.type.slice( 0, 8 ) === 'template' );
+
+			assert.equal( templateStrings.length, 2 );
+
+			let start = samples.misc.indexOf( '`the' ) + 1;
+			let end = samples.misc.indexOf( '${' );
+			let section = samples.misc.slice( start, end );
+
+			assert.deepEqual( templateStrings[0], {
+				start,
+				end,
+				value: section,
+				type: 'templateChunk'
+			});
+
+			start = samples.misc.indexOf( '}.' ) + 1;
+			end = samples.misc.indexOf( '\\``' ) + 2;
+			section = samples.misc.slice( start, end );
+
+			assert.deepEqual( templateStrings[1], {
+				start,
+				end,
+				value: section,
+				type: 'templateEnd'
+			});
+		});
+
+		it( 'finds normal strings', () => {
+			const strings = found.filter( chunk => chunk.type === 'string' );
+
+			assert.equal( strings.length, 2 );
+
+			let start = samples.misc.indexOf( "'" ) + 1;
+			let end = samples.misc.indexOf( "';" );
+			let string = samples.misc.slice( start, end );
+
+			assert.deepEqual( strings[0], {
+				start,
+				end,
+				value: string,
+				type: 'string'
+			});
+
+			start = samples.misc.indexOf( '"' ) + 1;
+			end = samples.misc.indexOf( '";' );
+			string = samples.misc.slice( start, end );
+
+			assert.deepEqual( strings[1], {
+				start,
+				end,
+				value: string,
+				type: 'string'
+			});
+		});
+	});
+
+	describe( 'erase', () => {
+		const tests = {
+			'erases a line comment': [
+				`const answer = 42; // line comment`,
+				`const answer = 42; //             `
+			],
+
+			'erases a line comment at the start of a line': [
+				`// line comment`,
+				`//             `
+			],
+
+			'erases a line comment with parens (#8)': [
+				`//)\n//\n`,
+				`// \n//\n`
+			],
+
+			'removes jsx contents': [
+				'<div>this should disappear</div>',
+				'<div>                     </div>'
+			],
+
+			'removes jsx attributes': [
+				'<div x="y"></div>',
+				'<div x=" "></div>'
+			],
+
+			'handles simple jsx syntax': [
+				`/**/<a>/**/{/**/}</a>/**/;`,
+				`/**/<a>    {/**/}</a>/**/;`
+			],
+
+			'erases block comments': [
+				`/*foo*/`,
+				`/*   */`
+			],
+
+			'erases regular expressions': [
+				`const regex = /you can ignore\\/[/]skip me, it's cool/;`,
+				`const regex = /                                     /;`
+			],
+
+			'erases template strings': [
+				"const templateString = `the answer is ${answer}. This is a backtick: \\``;",
+				"const templateString = `              ${answer}                        `;"
+			],
+
+			'erases normal strings': [
+				`const singleQuotedString = 'i\\'m trying to escape';`,
+				`const singleQuotedString = '                     ';`
+			],
+
+			'handles double trailing asterisks in block comments': [
+				'/* double trailing asterisks **/',
+				'/*                            */'
+			],
+
+			'handles comments before division': [
+				'1 /**/ / 2',
+				'1 /**/ / 2'
+			],
+
+			'handles comments before regex': [
+				`foo = 'bar'; /**/ /bar/.test(foo)`,
+				`foo = '   '; /**/ /   /.test(foo)`
+			],
+
+			'removes dollar inside template string': [
+				'const a = `$`;',
+				'const a = ` `;'
+			],
+
+			'removes escaped dollar inside template string': [
+				'const a = `\\$`;',
+				'const a = `  `;'
+			],
+
+			'removes things that look like template expressions': [
+				'const c = `$ {}${ `${ `$` }` }`;',
+				'const c = `    ${ `${ ` ` }` }`;'
+			],
+
+			'handles curlies inside a regex following export default (#1)': [
+				'export default /^}{/',
+				'export default /   /'
+			],
+
+			'handles / character after parenthesized expression': [
+				'( a + b ) / /x+/.exec( y )[0].length',
+				'( a + b ) / /  /.exec( y )[0].length'
+			],
+
+			'handles regex with escaped slash': [
+				'var regex = /\\//;',
+				'var regex = /  /;'
+			],
+
+			'handles slash after almost-keyword': [
+				`dodo / 'nonsense'`,
+				`dodo / '        '`
+			]
+		};
+
+		Object.keys( tests ).forEach( key => {
+			( key[0] === '-' ? it.only : it )( key, () => {
+				const [ before, after ] = tests[ key ];
+				assert.equal( tippex.erase( before ), after );
+			});
+		});
+
+		it( 'handles tricky regex/division cases', () => {
+			const erased = tippex.erase( samples.regexDivisionBefore );
+			assert.equal( erased, samples.regexDivisionAfter );
+		});
+
+		it( "handles jsx syntax", () => {
+			const erased = tippex.erase( samples.jsxBefore );
+			assert.equal( erased, samples.jsxAfter );
+		});
+
+		it( 'erases block comments', () => {
+			const erased = tippex.erase( samples.misc );
+			assert.equal( erased.indexOf( 'Multi' ), -1 );
+		});
+	});
+
+	describe( 'match', () => {
+		it( 'matches regular expressions against the original string', () => {
+			const importPattern = /import (\w+) from '([^']+)'/g;
+
+			let results = [];
+			tippex.match( samples.imports, importPattern, ( match, name, source ) => {
+				results.push({ match, name, source });
+			});
+
+			assert.deepEqual( results, [
+				{
+					match: "import a from './a.js'",
+					name: 'a',
+					source: './a.js'
+				},
+				{
+					match: "import c from './c.js'",
+					name: 'c',
+					source: './c.js'
+				}
+			]);
+		});
+
+		it( 'matches regular expressions without the global flag', () => {
+			const importPattern = /import (\w+) from '([^']+)'/;
+
+			let results = [];
+			tippex.match( samples.imports, importPattern, ( match, name, source ) => {
+				results.push({ match, name, source });
+			});
+
+			assert.deepEqual( results, [
+				{
+					match: "import a from './a.js'",
+					name: 'a',
+					source: './a.js'
+				}
+			]);
+		});
+	});
+
+	describe( 'replace', () => {
+		it( 'replaces a pattern', () => {
+			const importPattern = /import (\w+) from '([^']+)'/g;
+
+			var result = tippex.replace( samples.imports, importPattern, ( match, name, source ) => {
+				return `var ${name} = require('${source}')`;
+			});
+
+			assert.equal( result, samples.requires );
+		});
+	});
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-tippex.git



More information about the Pkg-javascript-commits mailing list