tippex-3.0.0/ 0000775 0000000 0000000 00000000000 13067601642 0013030 5 ustar 00root root 0000000 0000000 tippex-3.0.0/.eslintignore 0000664 0000000 0000000 00000000025 13067601642 0015530 0 ustar 00root root 0000000 0000000 test/samples/**/*.js
tippex-3.0.0/.eslintrc 0000664 0000000 0000000 00000001237 13067601642 0014657 0 ustar 00root root 0000000 0000000 {
"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"
}
}
tippex-3.0.0/.gitignore 0000664 0000000 0000000 00000000034 13067601642 0015015 0 ustar 00root root 0000000 0000000 .DS_Store
node_modules
dist
tippex-3.0.0/.travis.yml 0000664 0000000 0000000 00000000172 13067601642 0015141 0 ustar 00root root 0000000 0000000 sudo: false
language: node_js
node_js:
- "4"
- "stable"
env:
global:
- BUILD_TIMEOUT=10000
install: npm install
tippex-3.0.0/CHANGELOG.md 0000664 0000000 0000000 00000002777 13067601642 0014656 0 ustar 00root root 0000000 0000000 # 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
tippex-3.0.0/README.md 0000664 0000000 0000000 00000007344 13067601642 0014317 0 ustar 00root root 0000000 0000000 # 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.
tippex-3.0.0/package.json 0000664 0000000 0000000 00000002043 13067601642 0015315 0 ustar 00root root 0000000 0000000 {
"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"
}
}
tippex-3.0.0/rollup.config.js 0000664 0000000 0000000 00000000601 13067601642 0016144 0 ustar 00root root 0000000 0000000 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' }
]
};
tippex-3.0.0/src/ 0000775 0000000 0000000 00000000000 13067601642 0013617 5 ustar 00root root 0000000 0000000 tippex-3.0.0/src/index.js 0000664 0000000 0000000 00000022601 13067601642 0015265 0 ustar 00root root 0000000 0000000 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;
}
tippex-3.0.0/test/ 0000775 0000000 0000000 00000000000 13067601642 0014007 5 ustar 00root root 0000000 0000000 tippex-3.0.0/test/samples/ 0000775 0000000 0000000 00000000000 13067601642 0015453 5 ustar 00root root 0000000 0000000 tippex-3.0.0/test/samples/imports.js 0000664 0000000 0000000 00000000163 13067601642 0017506 0 ustar 00root root 0000000 0000000 import a from './a.js';
// import b from './b.js';
import c from './c.js';
const str = "import d from './d.js';";
tippex-3.0.0/test/samples/jsxAfter.jsx 0000664 0000000 0000000 00000001073 13067601642 0017770 0 ustar 00root root 0000000 0000000 {/**/}/**/;
{ {/**/}/**/ };
/**/ {/**/}/**/;
let x = {/**/}/**/;
foo( {/**/}, {/**/}, ( {/**/}))/**/;
{/**/}/**/;
/**/ {/**/}/**/;
let r = x < 10 /**/ && x > y ? 1 /**/ : 2;
if (x < 10) { {/**/}/**/ }/**/;
10/**/}> {x /**/ < 10 /**/ || x /**/ > 10 /**/} /**/;
; function x() { {/**/}/**/ } /**/;
; function x() { {/**/}/**/ }
/**/;
tippex-3.0.0/test/samples/jsxBefore.jsx 0000664 0000000 0000000 00000001073 13067601642 0020131 0 ustar 00root root 0000000 0000000 /**/{/**/}/**/;
{ /**/{/**/}/**/ };
/**//**/{/**/}/**/;
let x = /**/{/**/}/**/;
foo(/**/{/**/}, /**/{/**/}, (/**/{/**/}))/**/;
/**/{/**/}/**/;
/**//**/{/**/}/**/;
let r = x < 10 /**/ && x > y ? 1 /**/ : 2;
if (x < 10) { /**/{/**/}/**/ }/**/;
10/**/}>/**/{x /**/ < 10 /**/ || x /**/ > 10 /**/}/**//**/;
; function x() { /**/{/**/}/**/ } /**/;
; function x() { /**/{/**/}/**/ }
/**/;
tippex-3.0.0/test/samples/misc.js 0000664 0000000 0000000 00000001161 13067601642 0016743 0 ustar 00root root 0000000 0000000 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)
}
}
tippex-3.0.0/test/samples/regexDivisionAfter.js 0000664 0000000 0000000 00000000340 13067601642 0021607 0 ustar 00root root 0000000 0000000 / /;
/ /;
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;
tippex-3.0.0/test/samples/regexDivisionBefore.js 0000664 0000000 0000000 00000000340 13067601642 0021750 0 ustar 00root root 0000000 0000000 /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;
tippex-3.0.0/test/samples/requires.js 0000664 0000000 0000000 00000000171 13067601642 0017647 0 ustar 00root root 0000000 0000000 var a = require('./a.js');
// import b from './b.js';
var c = require('./c.js');
const str = "import d from './d.js';";
tippex-3.0.0/test/test.js 0000664 0000000 0000000 00000016403 13067601642 0015330 0 ustar 00root root 0000000 0000000 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': [
'this should disappear
',
'
'
],
'removes jsx attributes': [
'',
''
],
'handles simple jsx syntax': [
`/**//**/{/**/}/**/;`,
`/**/ {/**/}/**/;`
],
'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 );
});
});
});