├── .codeclimate.yml ├── .gitignore ├── .markdownlint.json ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── appveyor.yml ├── index.d.ts ├── package.json ├── src ├── JsccProps.d.ts ├── eval-expr.ts ├── get-expr.ts ├── jscc.ts ├── lib │ └── path-relative.ts ├── parse-buffer.ts ├── parse-chunks.ts ├── parse-helper.ts ├── parse-options.ts ├── parser.ts ├── regexes.ts └── remap-vars.ts ├── test ├── .eslintignore ├── expected │ ├── def-file-var.js │ ├── eslint-autofix.js │ ├── ex-object-properties.js │ ├── ex-simple-replacement.js │ ├── html-comments.html │ ├── html-short-cmts.html │ └── html-vars-js.html ├── fixtures │ ├── def-file-var.js │ ├── directive-ending.js │ ├── eslint-autofix.js │ ├── ex-file-and-date.js │ ├── ex-object-properties.js │ ├── ex-simple-replacement.js │ ├── html-comments.html │ ├── html-short-cmts.html │ ├── html-vars-js.html │ ├── utf8-bom.txt │ └── var-hide-output.js ├── helpers │ ├── concat-path.ts │ ├── preproc-str.ts │ ├── test-file-str.ts │ ├── test-file.ts │ ├── test-str.ts │ └── transform-file.ts ├── jscc.ts ├── mocha.opts ├── noversion │ └── package.json ├── s00-jscc.spec.ts ├── s04-cc.spec.ts ├── s06-replacement.spec.ts ├── s08-varnames.spec.ts ├── s10-options.spec.ts ├── s12.examples.spec.ts ├── s14-non-js.spec.ts ├── s16-async.spec.ts ├── test.env.js └── tsconfig.json ├── tsconfig.json └── tslint.json /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | method-complexity: 5 | enabled: true 6 | config: 7 | threshold: 8 8 | 9 | exclude_patterns: 10 | - "config/" 11 | - "cov-int/" 12 | - "coverage/" 13 | - "features/" 14 | - "script/" 15 | - "**/node_modules/" 16 | - "**/spec/" 17 | - "**/test/" 18 | - "**/*.d.ts" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude the transpiled code from the repo 2 | dist 3 | 4 | # Include sources and tests (it is excluded in .npmignore) 5 | !src 6 | !test 7 | 8 | # Share VSCode config, launcher, and tasks setup, but no more 9 | .vscode/**/* 10 | !.vscode/launch.json 11 | !.vscode/settings.json 12 | !.vscode/tasks.json 13 | 14 | # Common git and npm ignored folders and files 15 | .nyc_output 16 | .rpt2_cache 17 | *.pid 18 | *.seed 19 | *.tgz 20 | **/*.bak 21 | **/*.lcov 22 | **/*.log 23 | ~* 24 | cov-int 25 | coverage 26 | lib-cov 27 | logs 28 | node_modules 29 | package-lock.json 30 | pids 31 | yarn.lock 32 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "line-length": false, 4 | "no-duplicate-header": { "siblings_only": true }, 5 | "no-inline-html": false 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | - "6.0.0" 6 | 7 | branches: 8 | only: 9 | - master 10 | - dev 11 | 12 | git: 13 | depth: 3 14 | quiet: true 15 | 16 | before_script: 17 | - make setup_cover 18 | 19 | script: 20 | - yarn test 21 | 22 | after_script: 23 | - make send_cover 24 | 25 | notifications: 26 | email: false 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Iniciar", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 9 | "stopOnEntry": false, 10 | "args": ["--no-timeouts", "--check-leaks", "${workspaceRoot}/test/*.ts"], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": "npm: tsc", 13 | "runtimeExecutable": null, 14 | "runtimeArgs": ["--nolazy"], 15 | "env": { 16 | "NODE_ENV": "testing" 17 | }, 18 | "console": "internalConsole", 19 | "internalConsoleOptions": "openOnSessionStart", 20 | "sourceMaps": true 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | /* 2 | TSLint deshabilitado a nivel proyecto 3 | */ 4 | { 5 | "typescript.disableAutomaticTypeAcquisition": true, 6 | "typescript.format.insertSpaceAfterConstructor": true, 7 | "typescript.format.insertSpaceBeforeFunctionParenthesis": true, 8 | "typescript.suggest.completeFunctionCalls": true, 9 | "typescript.tsdk": "node_modules/typescript/lib" 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes for jscc 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## \[1.2.0] - 2018-12-28 6 | 7 | ### Added 8 | 9 | - More tests. 10 | - markdownlint config. 11 | - perf-regexes as dependency, for the `JS_STRINGS` regex. 12 | - skip-regex as dependency to help solving #8 13 | 14 | ### Changed 15 | 16 | - Revised .gitignore 17 | - Update dependencies and devDependencies. 18 | - Update Readme. 19 | - Replace node 10 with the 'node' in travis config. 20 | 21 | ### Fixed 22 | 23 | - Regex in remap-vars being copied by reference. 24 | - #8 removal of trailing comment is breaking expressions. 25 | - tslint errors. 26 | 27 | ### Removed 28 | 29 | - .npmignore, now using package.json 'files' property. 30 | - unused ESLint configuration. 31 | 32 | ## \[1.1.0] - 2018-11-22 33 | 34 | ### Added 35 | 36 | - Option `escapeQuotes` to escape quotes in the output of strings (not wrapped by JSON output). 37 | - TSLint instead of ESLint, for compatibility with CI services. 38 | - [Codacy](https://api.codacy.com) quality and coverage services. 39 | 40 | ### Changed 41 | 42 | - Convert `export.default` to `module.exports` in internal modules. Since it is a node.js library, it looks right and produces a cleaner code. 43 | - The output of chained properties stops with a primitive value, to avoid some compile-time errors. 44 | - Updated Readme, add "vulnerabilities" badge from [snyk.io](https://snyk.io). 45 | - Regression of the replacement of `NaN` with `null` since the later alters the behavior of the Date ctor. 46 | - Simplify the `parseChunk` function, logic moved to the `parseHelper` class. 47 | 48 | ### Removed 49 | 50 | - ESLint configuration. 51 | - Coverity badge, get the right results with this service is a nightmare. 52 | 53 | ### Fixed 54 | 55 | - Minor issues with linters. 56 | 57 | ## \[1.0.0] - 2018-10-23 58 | 59 | Major refactorization after two years, using TypeScript v3. 60 | 61 | ### Added 62 | 63 | - Support for BOM mark in the source (it is preserved and does not affects the parsing). 64 | - Badges of the different services used to take care of the quality of the code. 65 | - Buy me a Coffee link. 66 | - Support for replacement with instances of `Number`. 67 | - Share .vscode setup for launch, settings, and tasks in CVS. 68 | - .npmignore files, for distribution with minimal stuff. 69 | - Sync test for async operation. 70 | - Async operation. 71 | - ~~Add prefix for ` ./cc-test-reporter 10 | @ chmod +x ./cc-test-reporter 11 | @ ./cc-test-reporter before-build 12 | endif 13 | 14 | send_cover: 15 | ifeq ($(CURBUILD),$(REQBUILD)) 16 | @ echo Sending coverage report... 17 | @ nyc report -r=lcov 18 | @ codecov -f ./coverage/lcov.info 19 | @ ./cc-test-reporter after-build --exit-code $(TRAVIS_TEST_RESULT) 20 | @ cat ./coverage/lcov.info | codacy-coverage -p . --language typescript 21 | @ echo The report was sent. 22 | else 23 | @ echo The coverage report will be sent in $(REQBUILD) 24 | endif 25 | 26 | .PHONY: setup_cover send_cover 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jscc 2 | 3 | [![jscc on npm][npm-badge]][npm-url] 4 | [![License MIT][license-badge]][license-url] 5 | [![Linux Build][travis-badge]][travis-url] 6 | [![Codacy][codacy-badge]][codacy-url] 7 | [![Coverage][codecov-badge]][codecov-url] 8 | 9 | Featuring some of the C preprocessor characteristics through special, configurable comments, jscc can be used in any type of files to build multiple versions of your software from the same code base. 10 | 11 | With jscc, you have: 12 | 13 | - Conditional inclusion/exclusion of blocks, based on compile-time variables* 14 | - Compile-time variables with all the power of JavaScript expressions 15 | - Replacement of variables in the sources, by its value at compile-time 16 | - Sourcemap support, useful for JavaScript sources. 17 | - TypeScript v3 definitions 18 | 19 | \* This feature allows you the conditional declaration of ES6 imports (See the [example](#example)). 20 | 21 | jscc is derived on [jspreproc](http://amarcruz.github.io/jspreproc), the tiny source file preprocessor in JavaScript, enhanced with sourcemap support but without the file importer nor the removal of comments ([rollup](https://rollupjs.org/guide/en) with [rollup-plugin-cleanup](https://www.npmjs.com/package/rollup-plugin-cleanup) does it better). 22 | 23 | jscc works in NodeJS 6 or later, with minimal dependencies and footprint. It was designed to operate on small to medium pieces of code (like most nowadays) and, since the whole process is done in memory, it is _really fast_. 24 | 25 | jscc is **not** a minifier tool, but it does well what it does... 26 | 27 | ## Install 28 | 29 | Use the instructions of the plugin for your toolchain: 30 | 31 | - [Rollup](https://www.npmjs.com/package/rollup-plugin-jscc) 32 | - [Brunch](https://www.npmjs.com/package/jscc-brunch) 33 | - [Browserify](https://www.npmjs.com/package/jsccify) 34 | - [Gulp](https://www.npmjs.com/package/gulp-jscc) 35 | - [WebPack](https://github.com/OrangeLab/webpack-plugin-jscc) - Thanks to @duanlikang 36 | 37 | or install the jscc package from npm if you need direct access to its API: 38 | 39 | ```sh 40 | npm i jscc -D 41 | ``` 42 | 43 | ### Direct Usage 44 | 45 | ```js 46 | const jscc = require('jscc'); 47 | 48 | const result = jscc(sourceCode, options); 49 | 50 | // or in async mode: 51 | jscc(sourceCode, options, (err, result) => { 52 | if (err) { 53 | console.error(err); 54 | } else { 55 | console.log(result.code); 56 | console.log(result.map); 57 | } 58 | }) 59 | ``` 60 | 61 | The result is a plain JS object with a property `code`, a string with the processed source, and a property `map`, with a raw sourcemap object, if required by the `sourcemap` option (its default is `true`). 62 | 63 | If a callback is provided, jscc will operate asynchronously and call the callback with an error object, if any, or `null` in the first parameter and the result in the second. 64 | 65 | Please see the Wiki to know the supported [options](https://github.com/aMarCruz/jscc/wiki/Options). 66 | 67 | ## Directives 68 | 69 | jscc works with _directives_ inserted in the text and prefixed with configurable character sequences, that defaults to `'/*'`, `'//'` and `' 189 | [npm-badge]: https://img.shields.io/npm/v/jscc.svg 190 | [npm-url]: https://www.npmjs.com/package/jscc 191 | [license-badge]: https://img.shields.io/npm/l/jscc.svg?colorB=blue 192 | [license-url]: https://github.com/aMarCruz/jscc/blob/master/LICENSE 193 | [appveypr-badge]: https://ci.appveyor.com/api/projects/status/hdsef0p6q0oqr127?svg=true 194 | [appveypr-url]: https://ci.appveyor.com/project/aMarCruz/jscc 195 | [travis-badge]: https://img.shields.io/travis/aMarCruz/jscc.svg?label=travis 196 | [travis-url]: https://travis-ci.org/aMarCruz/jscc 197 | [snyk-badge]: https://snyk.io/test/github/aMarCruz/jscc/badge.svg?targetFile=package.json 198 | [snyk-url]: https://snyk.io/test/github/aMarCruz/jscc?targetFile=package.json 199 | [codacy-badge]: https://img.shields.io/codacy/grade/30e8679fcd614227837ad250dd6c4030.svg 200 | [codacy-url]: https://www.codacy.com/app/aMarCruz/jscc?utm_source=github.com&utm_medium=referral&utm_content=aMarCruz/jscc&utm_campaign=Badge_Grade 201 | [codecov-badge]: https://img.shields.io/codecov/c/github/aMarCruz/jscc.svg 202 | [codecov-url]: https://codecov.io/gh/aMarCruz/jscc 203 | [climate-badge]: https://codeclimate.com/github/aMarCruz/jscc/badges/gpa.svg 204 | [climate-url]: https://codeclimate.com/github/aMarCruz/jscc 205 | [commits-badge]: https://img.shields.io/github/last-commit/aMarCruz/jscc.svg 206 | [commits-url]: https://github.com/aMarCruz/jscc/commits/master 207 | [kofi-url]: https://ko-fi.com/C0C7LF7I 208 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | version: "{build}" 4 | 5 | clone_depth: 10 6 | 7 | init: 8 | - git config --global core.autocrlf false 9 | 10 | # Test against this version of Node.js 11 | environment: 12 | matrix: 13 | # node.js 14 | - nodejs_version: "10" 15 | - nodejs_version: "6.0" 16 | 17 | matrix: 18 | fast_finish: false 19 | 20 | # Install scripts. (runs after repo cloning) 21 | install: 22 | # Get the latest stable version of Node.js or io.js 23 | - ps: Install-Product node $env:nodejs_version 24 | # install modules 25 | - npm install 26 | 27 | # Post-install test scripts. 28 | test_script: 29 | # Output useful info for debugging. 30 | - node --version && npm --version 31 | # run tests 32 | - npm test 33 | 34 | # Don't actually build. 35 | build: off 36 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Type definitions for jscc v1.1.1 3 | @license MIT 4 | */ 5 | export = Jscc 6 | 7 | /** 8 | * Preprocessor for conditional comments and replacement of compile-time 9 | * variables in text files (asynchronous version). 10 | * 11 | * @param source String to be preprocessed, encoded in utf8. 12 | * @param filename Absolute or relative path to the current directory. 13 | * @param options User options. 14 | * @param callback NodeJS style callback that receives the error and result as parameters. 15 | */ 16 | declare function Jscc ( 17 | source: string, 18 | filename: string | null | undefined, 19 | options: Jscc.Options | null | undefined, 20 | callback: Jscc.Callback 21 | ): void 22 | 23 | /** 24 | * Preprocessor for conditional comments and replacement of compile-time 25 | * variables in text files (synchronous version). 26 | * 27 | * @param source String to be preprocessed, encoded in utf8. 28 | * @param filename Absolute or relative path to the current directory. 29 | * @param options User options. 30 | * @returns Object with `code` and `map` properties. 31 | */ 32 | declare function Jscc ( 33 | source: string, 34 | filename?: string | null, 35 | options?: Jscc.Options | null 36 | ): Jscc.Result 37 | 38 | // tslint:disable:no-namespace 39 | declare namespace Jscc { 40 | 41 | type QuoteType = 'single' | 'double' | 'both' 42 | 43 | interface Options { 44 | /** 45 | * String with the type of quotes to escape in the output of strings: 46 | * 'single', 'double' or 'both'. 47 | * 48 | * It does not affects the strings contained in the JSON output of 49 | * objects. 50 | */ 51 | escapeQuotes?: QuoteType 52 | 53 | /** 54 | * Allows to preserve the empty lines of directives and blocks that 55 | * were removed. 56 | * 57 | * Use this option with `sourceMap:false` if you are interested only in 58 | * preserve the line count. 59 | * 60 | * @default false 61 | */ 62 | keepLines?: boolean 63 | 64 | /** 65 | * Include the original source in the sourcemap. 66 | * 67 | * @default false 68 | */ 69 | mapContent?: boolean 70 | 71 | /** 72 | * Makes a hi-res sourcemap. 73 | * 74 | * @default true 75 | */ 76 | mapHires?: boolean 77 | 78 | /** 79 | * String, regex or array of strings or regex matching the start of a directive. 80 | * That is, the characters before the '#', usually the start of comments. 81 | * 82 | * @default ['//','/*',' 6 | 7 | 8 | 9 | My App 10 | 11 | 12 | 15 |

My App

16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/expected/html-short-cmts.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | My App 10 | 11 | 12 | 15 |

My App

16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/expected/html-vars-js.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | My App 10 | 11 | 12 | 15 |

My App

16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/fixtures/def-file-var.js: -------------------------------------------------------------------------------- 1 | // $_FILE 2 | // _$_FILE $__FILE $FILE _FILE$ 3 | export default function main () { 4 | // out from "$_FILE" 5 | } 6 | //$_FILE -------------------------------------------------------------------------------- /test/fixtures/directive-ending.js: -------------------------------------------------------------------------------- 1 | //#if '//foo' === "//bar" // comment 2 | false 3 | //#endif //comment 4 | //#if "//" === "//"//comment 5 | true 6 | //#endif 7 | //#if 0// comment // 8 | false 9 | //#endif//comment 10 | -------------------------------------------------------------------------------- /test/fixtures/eslint-autofix.js: -------------------------------------------------------------------------------- 1 | // #set _DEBUG = 1 // this is a jscc comment 2 | 3 | /* #if _DEBUG // closing this multiline comment */ 4 | // #if process.env.devmode === 'production' 5 | // #set _DEBUG 0 // the `=` is optional 6 | // #else anything after `#else` or `#endif` is ignored 7 | /* eslint-disable no-console */ 8 | console.log('Debug mode on.'); 9 | // #endif 10 | // #endif _DEBUG '_DEBUG' is ignored 11 | -------------------------------------------------------------------------------- /test/fixtures/ex-file-and-date.js: -------------------------------------------------------------------------------- 1 | //#set _DATE = new Date().toISOString().slice(0, 10) 2 | /* 3 | File: $_FILE 4 | Date: $_DATE 5 | */ 6 | -------------------------------------------------------------------------------- /test/fixtures/ex-object-properties.js: -------------------------------------------------------------------------------- 1 | //#set _OBJ { prop: 1, nested: { prop2: 2 } } 2 | console.log($_OBJ.prop); // outputs 1 3 | console.log($_OBJ); // outputs { "prop": 1, "nested": { "prop2": 2 } } 4 | console.log($_OBJ.foo); // outputs undefined 5 | console.log($_OBJ.nested); // outputs { "prop2": 2 } 6 | console.log($_OBJ.nested.prop2); // outputs 2 7 | -------------------------------------------------------------------------------- /test/fixtures/ex-simple-replacement.js: -------------------------------------------------------------------------------- 1 | //#set _FOO 'foo' 2 | 3 | let bar = '$_FOO'.toUpperCase(); // bar = 'FOO' 4 | let baz = { $_FOO: 'bar' }; // baz = { foo: 'bar' } 5 | 6 | console.log('$_FOO'); // outputs 'foo' 7 | console.log(baz['$_FOO']); // outputs 'bar' 8 | console.log(baz.$_FOO); // outputs 'bar' 9 | -------------------------------------------------------------------------------- /test/fixtures/html-comments.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | $_TITLE 11 | 12 | 13 | 16 |

$_TITLE

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/html-short-cmts.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | $_TITLE 11 | 12 | 13 | 16 |

$_TITLE

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/html-vars-js.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | //#set _SUFFIX 12345 7 | 8 | 9 | 10 | $_TITLE 11 | 12 | 13 | 16 |

$_TITLE

17 | 18 | //#if _DEBUG 19 | 20 | //#else 21 | 22 | //#endif 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/utf8-bom.txt: -------------------------------------------------------------------------------- 1 | //#if 0 2 | BOM 3 | //#else 4 | OK 5 | //#endif 6 | -------------------------------------------------------------------------------- /test/fixtures/var-hide-output.js: -------------------------------------------------------------------------------- 1 | //#if _DEBUG 2 | import { debugOut as $_DEBUGOUT } from 'debug-out' 3 | //#else 4 | //#set _DEBUGOUT '//' 5 | //#endif 6 | $_DEBUGOUT('DEBUG') 7 | -------------------------------------------------------------------------------- /test/helpers/concat-path.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const root = path.dirname(__dirname) 4 | 5 | export const concatPath = (folder: string, name: string) => { 6 | let file = path.join(root, folder, name) 7 | 8 | file = file.replace(/\\/g, '/') 9 | if (!path.extname(file)) { 10 | file += '.js' 11 | } 12 | 13 | return file 14 | } 15 | -------------------------------------------------------------------------------- /test/helpers/preproc-str.ts: -------------------------------------------------------------------------------- 1 | import jscc from '../jscc' 2 | 3 | /** 4 | * Run jscc with the given source and options and return the resulting text 5 | * without trimming it. 6 | * 7 | * @param code Source 8 | * @param opts jscc options 9 | */ 10 | export const preprocStr = (code: string, opts?: jscc.Options) => jscc(code, '', opts).code 11 | -------------------------------------------------------------------------------- /test/helpers/test-file-str.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import jscc from '../jscc' 3 | import { transformFile } from './transform-file' 4 | 5 | export const testFileStr = (file: string, expected: string | RegExp, opts?: jscc.Options) => { 6 | const result = transformFile(file, opts) 7 | 8 | if (expected instanceof RegExp) { 9 | expect(result).to.match(expected) 10 | } else { 11 | expect(result).to.be(expected) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/test-file.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import fs from 'fs' 3 | import jscc from '../jscc' 4 | 5 | import { concatPath } from './concat-path' 6 | import { transformFile } from './transform-file' 7 | 8 | const getExpected = (file: string) => fs 9 | .readFileSync(concatPath('expected', file), 'utf8').replace(/\s+$/, '') 10 | 11 | export const testFile = (file: string, opts?: jscc.Options, save?: boolean) => { 12 | const expected = getExpected(file) 13 | const result = transformFile(file, opts) 14 | 15 | expect(result).to.be.a('string') 16 | if (save) { 17 | throw new Error('If mocha is not watching, comment this to save the file.') 18 | // fs.writeFileSync(concatPath('expected', file + '_out.js'), result || '') 19 | } 20 | 21 | expect(result).to.be(expected) 22 | } 23 | -------------------------------------------------------------------------------- /test/helpers/test-str.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import jscc from '../jscc' 3 | 4 | export const testStr = ( 5 | source: string | string[], 6 | expected: string | RegExp, 7 | opts?: jscc.Options 8 | ) => { 9 | 10 | if (Array.isArray(source)) { 11 | source = source.join('\n') 12 | } 13 | 14 | const code = jscc(source, '', opts).code.replace(/\s+$/, '') // trimRight 15 | 16 | if (expected instanceof RegExp) { 17 | expect(code).to.match(expected) 18 | } else { 19 | expect(code).to.be(expected) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /test/helpers/transform-file.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import jscc from '../jscc' 3 | import { concatPath } from './concat-path' 4 | 5 | export const transformFile = (file: string, opts?: jscc.Options) => { 6 | const inFile = concatPath('fixtures', file) 7 | const code = fs.readFileSync(inFile, 'utf8') 8 | 9 | return jscc(code, inFile, opts).code.replace(/\s+$/, '') 10 | } 11 | -------------------------------------------------------------------------------- /test/jscc.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Proxy for ESM style operation without esModuleInterop. 3 | */ 4 | import _jscc = require('..') 5 | export default _jscc 6 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | # Set the must simple report and other options 2 | --check-leaks 3 | --require ./test/test.env.js 4 | --require ts-node/register 5 | --watch-extensions ts 6 | -------------------------------------------------------------------------------- /test/noversion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noversion", 3 | "description": "For testing a package.json without version" 4 | } 5 | -------------------------------------------------------------------------------- /test/s00-jscc.spec.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import path from 'path' 3 | 4 | // common helpers 5 | import { preprocStr } from './helpers/preproc-str' 6 | import { testFile } from './helpers/test-file' 7 | import { testFileStr } from './helpers/test-file-str' 8 | import { testStr } from './helpers/test-str' 9 | 10 | process.chdir(__dirname) 11 | 12 | describe('jscc', function () { 13 | 14 | it('by default uses JavaScript comments to start directives', function () { 15 | testStr([ 16 | '//#unset _FOO', 17 | '/*#set _FOO 1', 18 | '$_FOO', 19 | ], '1') 20 | }) 21 | 22 | it('the predefined varname `_FILE` is the relative path of the current file', function () { 23 | testFile('def-file-var') 24 | }) 25 | 26 | it('any user-defined `_FILE` is overwritten by the current filename (even if empty)', function () { 27 | testFile('def-file-var', { 28 | values: { _FILE: 'my-code.js' }, 29 | }) 30 | }) 31 | 32 | it('`_VERSION` comes from the package.json in the current or upper path', function () { 33 | const version = require('../package.json').version as string 34 | testStr('/* @version $_VERSION */', `/* @version ${version} */`) 35 | }) 36 | 37 | it('`_VERSION` ignores package.json without a `version` property', function () { 38 | const version = require('../package.json').version as string 39 | const cwdir = __dirname 40 | 41 | process.chdir(path.join(cwdir, 'noversion')) 42 | const result = preprocStr('$_VERSION') 43 | process.chdir(cwdir) 44 | expect(result).to.be(version) 45 | }) 46 | 47 | it('`_VERSION` must be empty if no package.json `version` was found', function () { 48 | const cwdir = __dirname 49 | 50 | process.chdir(path.resolve('/')) 51 | const result = preprocStr('$_VERSION') 52 | process.chdir(cwdir) 53 | expect(result).to.be('') 54 | }) 55 | 56 | it('any non-empty user defined `_VERSION` must be preserved', function () { 57 | testStr('$_VERSION', /^@$/, { values: { _VERSION: '@' } }) 58 | }) 59 | 60 | it('empty user defined `_VERSION` must be preserved, if it is a string', function () { 61 | testStr('$_VERSION', '', { values: { _VERSION: '' } }) 62 | }) 63 | 64 | it('user defined `_VERSION` must be overwritten, if it is not a string', function () { 65 | const version = require('../package.json').version as string 66 | testStr('$_VERSION', version, { values: { _VERSION: null } }) 67 | testStr('$_VERSION', version, { values: { _VERSION: true } }) 68 | }) 69 | 70 | it('support conditional comments with the `#if expression` syntax', function () { 71 | testStr([ 72 | '//#if _FALSE', 73 | 'false', 74 | '//#endif', 75 | '//#if _TRUE', 76 | 'OK', 77 | '//#endif', 78 | ], 'OK', { 79 | values: { _TRUE: true }, 80 | }) 81 | }) 82 | 83 | it('directives ends at the end of the line or the first unquoted `//`', function () { 84 | testFileStr('directive-ending', 'true') 85 | }) 86 | 87 | it('must preserve Windows line-endings', function () { 88 | const code = [ 89 | '//#set _A 1', 90 | '//#if _A', 91 | 'Win', 92 | '//#endif', 93 | 'OK', 94 | '', 95 | ].join('\r\n') 96 | expect(preprocStr(code)).to.be('Win\r\nOK\r\n') 97 | }) 98 | 99 | it('must preserve Windows line-endings (2)', function () { 100 | const code = [ 101 | '//#if 1', 102 | 'Win', 103 | 'OK', 104 | '//#endif', 105 | '', 106 | ].join('\r\n') 107 | expect(preprocStr(code)).to.be('Win\r\nOK\r\n') 108 | }) 109 | 110 | it('must preserve Mac line-endings', function () { 111 | const code = [ 112 | '//#set _A 1', 113 | '//#if _A', 114 | 'Mac', 115 | '//#endif', 116 | 'OK', 117 | '', 118 | ].join('\r') 119 | expect(preprocStr(code)).to.be('Mac\rOK\r') 120 | }) 121 | 122 | it('must preserve Mac line-endings (2)', function () { 123 | const code = [ 124 | '//#if 1', 125 | 'Mac', 126 | 'OK', 127 | '//#endif', 128 | '', 129 | ].join('\r') 130 | expect(preprocStr(code)).to.be('Mac\rOK\r') 131 | }) 132 | 133 | it('must preserve tuf8 BOM in the source', function () { 134 | // Seems nodeJS uses \uFEFF to mark any enconding 135 | testFileStr('utf8-bom.txt', /^\ufeffOK$/) 136 | expect(preprocStr('\ufeff')).to.be('\ufeff') 137 | }) 138 | 139 | }) 140 | -------------------------------------------------------------------------------- /test/s04-cc.spec.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | 3 | import { preprocStr } from './helpers/preproc-str' 4 | import { testFileStr } from './helpers/test-file-str' 5 | import { testStr } from './helpers/test-str' 6 | 7 | describe('Conditional Compilation', function () { 8 | 9 | it('has the pair `#if`/`#endif` for basic conditional blocks', function () { 10 | testStr([ 11 | '//#if 1', 12 | 'OK', 13 | '//#endif', 14 | ], 'OK') 15 | }) 16 | 17 | it('supports `#elif expression`', function () { 18 | testStr([ 19 | '//#set _FOO = 2', 20 | '//#if !_FOO', 21 | 'error', 22 | '//#elif _FOO===1+1', 23 | 'OK', 24 | '//#elif _FOO===2', 25 | 'error', 26 | '//#endif', 27 | ], 'OK') 28 | }) 29 | 30 | it('supports `#elif` expression with `#ifset` and `#ifnset`', function () { 31 | testStr([ 32 | '//#set _FOO = 2', 33 | '//#ifnset _FOO', 34 | 'error', 35 | '//#elif _FOO===2', 36 | 'OK', 37 | '//#endif', 38 | '//#ifset _FOO_2', 39 | 'error', 40 | '//#elif 1', 41 | 'OK', 42 | '//#endif', 43 | ], 'OK\nOK') 44 | }) 45 | 46 | it('support the `#else` directive', function () { 47 | testStr([ 48 | '//#if 0', 49 | 'error', 50 | '//#else', 51 | 'OK', 52 | '//#endif', 53 | ], 'OK') 54 | }) 55 | 56 | it('and the `#elif` directive', function () { 57 | testStr([ 58 | '//#set _FOO 2', 59 | '//#if _FOO === 0', 60 | 'false', 61 | '//#elif _FOO === 1', 62 | 'false', 63 | '//#elif _FOO === 2', 64 | 'OK', 65 | '//#else', 66 | 'false', 67 | '//#endif', 68 | ], 'OK') 69 | }) 70 | 71 | it('have `#ifset` for testing if variable exists (even undefined values)', function () { 72 | testStr([ 73 | '//#ifset _UNDEF', 74 | 'false', 75 | '//#endif', 76 | '//#set _UNDEF undefined', 77 | '//#ifset _UNDEF', 78 | 'OK', 79 | '//#endif', 80 | ], 'OK') 81 | }) 82 | 83 | it('and `#ifnset` for testing if variable NOT exists', function () { 84 | testStr([ 85 | '//#ifnset _THIS_IS_UNSET', 86 | 'OK', 87 | '//#endif', 88 | '//#set _THIS_IS_SET', 89 | '//#ifnset _THIS_IS_SET', 90 | 'error', 91 | '//#endif', 92 | ], 'OK') 93 | }) 94 | 95 | it('`#error` throws an exception with a custom message', function () { 96 | expect(function () { 97 | preprocStr('//#error "boom" + "!"') 98 | }).to.throwError(/boom!/) 99 | }) 100 | 101 | it('blocks can be nested', function () { 102 | testStr([ 103 | '//#set _FOO 2', 104 | '//#set _BAR 2', 105 | '//#set _BAZ 2', 106 | '', 107 | '//#if _FOO == 1 // false', 108 | '//#elif _FOO == 2 // true', 109 | 'OK1', 110 | ' //#if _BAR == 2 // true', 111 | 'OK2', 112 | ' //#if _BAZ == 1 // false', 113 | 'NO', 114 | ' //#else', 115 | 'OK3', 116 | ' //#endif', 117 | ' //#endif', 118 | '//#endif', 119 | ], '\nOK1\nOK2\nOK3') 120 | }) 121 | 122 | it('must ignore directives inside removed blocks', function () { 123 | testStr([ 124 | '//#if 1', 125 | 'OK', 126 | '//#else', 127 | ' //#if 1', 128 | 'NO', 129 | ' //#endif', 130 | '//#endif', 131 | ], 'OK') 132 | }) 133 | 134 | it('`#else` and `#endif` ignores anything in their line', function () { 135 | testStr([ 136 | '//#if 0', 137 | 'false', 138 | '//#else this is ignored', 139 | 'OK', 140 | '//#endif and this', 141 | ], 'OK') 142 | }) 143 | 144 | it('for other directives, supports a comment after the expression', function () { 145 | testStr([ 146 | '//#set _V 1 // works', 147 | '//#if 0 // ok', 148 | 'Error', 149 | '//#elif _V===1// no need space', 150 | 'OK', 151 | '//#endif', 152 | ], 'OK') 153 | }) 154 | 155 | it('string content should not be confused with a comment', function () { 156 | testStr([ 157 | '//#set _V "://" //cmnt1', 158 | '//#if _V==="://" //cmnt2', 159 | 'OK', 160 | '//#endif', 161 | ], 'OK') 162 | }) 163 | 164 | it('regex content should not be confused with a comment (#8)', function () { 165 | testStr([ 166 | '//#set _R /\\// //cmnt1', 167 | '$_R', 168 | ], '\\/') 169 | }) 170 | 171 | it('ES6TL content should not be confused with a comments (#8)', function () { 172 | testStr([ 173 | '//#set _V `${{foo:\'//\'}} //` //cmnt1', 174 | 'OK', 175 | ], 'OK') 176 | }) 177 | 178 | it('must handle nested ES6 TL within expressions', function () { 179 | testStr([ 180 | '//#set _V `${{foo:`//`}} //` //cmnt1', 181 | 'OK', 182 | ], 'OK') 183 | }) 184 | 185 | it('must handle escaped brackets within ES6 TL', function () { 186 | testStr([ 187 | '//#set _V `\\${$\\{ ${" OK "} \\}` //', 188 | '$_V', 189 | ], '${${ OK }') 190 | }) 191 | 192 | it('can open a multiline comment with one directive and close it with other', function () { 193 | testStr([ 194 | '/*#if 0 // This one opens the multiline comment', 195 | 'Error', 196 | '//#else This other closes the multiline comment */', 197 | 'OK', 198 | '//#endif', 199 | ], 'OK') 200 | }) 201 | 202 | it('anything inside multiline comment is revealed if the expr is trueish', function () { 203 | testStr([ 204 | '/*#if 1 // open the multiline comment, it will be true', 205 | 'OK', 206 | '//#else closes the comment, but "OK" will be visible */', 207 | 'Error', 208 | '//#endif', 209 | ], 'OK') 210 | }) 211 | 212 | it('can hide all the output enclosing it in a falsy block', function () { 213 | testStr('//#if false\nfoo()\n//#endif', '') 214 | }) 215 | 216 | it('can comment output lines (usefull to hide console.* and params)', function () { 217 | testFileStr('var-hide-output', "//('DEBUG')") 218 | }) 219 | 220 | it('...or can reveal lines if the condition is trueish', function () { 221 | testFileStr('var-hide-output', /import [\S\s]+\$_DEBUGOUT\('DEBUG'\)/, { values: { _DEBUG: 1 } }) 222 | }) 223 | 224 | it('must handle the trailing comment in any directive', function () { 225 | const source = [ 226 | '//#unset _A//x', 227 | '//#set _A//x', 228 | '//#set _A=1//x', 229 | '//#if !_A//x', 230 | '//#elif _A//x', 231 | 'OK', 232 | '//#else//x', 233 | 'false', 234 | '//#endif//x', 235 | ].join('\n') 236 | testStr(source, 'OK') 237 | }) 238 | 239 | it('must handle the trailing comment even in `#error` expressions', function () { 240 | expect(function () { 241 | preprocStr('//#error "boom"//x') 242 | }).to.throwError(/boom\b/) 243 | }) 244 | 245 | it('does not confuse comments that seems like directives', function () { 246 | const source = [ 247 | '///#set _A 1', 248 | 'false', 249 | '//*#set _A', 250 | 'false', 251 | ].join('\n') 252 | testStr(source, source) 253 | }) 254 | 255 | it('does not confuse comments that seems like directives (2)', function () { 256 | testStr([ 257 | '//#if 1//#else', 258 | 'OK', 259 | '//#elif 1//#endif', 260 | 'OK', 261 | '//#endif \t//#endif', 262 | ], 'OK') 263 | }) 264 | 265 | }) 266 | 267 | //#endregion Conditional Compilation 268 | //#region Conditional Compilation Errors ------------------------------------- 269 | 270 | describe('Conditional Compilation must throw on...', function () { 271 | 272 | it('unclosed conditional blocks', function () { 273 | expect(function () { 274 | preprocStr('//#if _FOO\n#endif') 275 | }).to.throwError(/Unexpected end of file/) 276 | }) 277 | 278 | it('unbalanced block', function () { 279 | expect(function () { 280 | preprocStr('//#if true\n//#elif 1') 281 | }).to.throwError(/Unexpected end of file/) 282 | }) 283 | 284 | it('unbalanced blocks even inside removed blocks', function () { 285 | expect(() => { 286 | preprocStr([ 287 | '//#if 1', 288 | 'OK', 289 | '//#else', 290 | ' //#if 1', 291 | '//#endif', 292 | ].join('\n')) 293 | }).to.throwError(/Unexpected end of file/) 294 | }) 295 | 296 | it('`#elif` without its previous `#if`', function () { 297 | expect(() => { 298 | preprocStr('#if 1\n//#elif 1\n//#endif') 299 | }).to.throwError(/Unexpected #elif/) 300 | }) 301 | 302 | it('`#elif` inside `#else`', function () { 303 | expect(() => { 304 | preprocStr('//#if 1\n//#else\n//#elif 1\n//#endif') 305 | }).to.throwError(/Unexpected #elif/) 306 | }) 307 | 308 | it('`#else` after `#else`', function () { 309 | expect(() => { 310 | preprocStr('//#if 1\n//#else\n//#else\n') 311 | }).to.throwError(/Unexpected #else/) 312 | }) 313 | 314 | it('`#endif` without a previous block', function () { 315 | expect(() => { 316 | preprocStr('#if 1\n//#endif\n') 317 | }).to.throwError(/Unexpected #endif/) 318 | }) 319 | 320 | it('`#endif` widthout a previous block (duplicated #endif)', function () { 321 | expect(function () { 322 | preprocStr('//#if 1\n//#endif\n//#endif') 323 | }).to.throwError(/Unexpected #endif/) 324 | }) 325 | 326 | it('directive without expression throws "Expression expected"', function () { 327 | expect(function () { 328 | preprocStr('//#if\n//#endif') 329 | }).to.throwError(/Expression expected/) 330 | }) 331 | 332 | it('directive without expression inside removed blocks', function () { 333 | expect(function () { 334 | preprocStr([ 335 | '//#if 1', 336 | 'OK', 337 | '//#else', 338 | ' //#if', 339 | ' //#endif', 340 | '//#endif', 341 | ].join('\n')) 342 | }).to.throwError(/Expression expected/) 343 | }) 344 | 345 | it('evaluating unclosed brackets in expression', function () { 346 | expect(function () { 347 | preprocStr('//#set _V { //') 348 | }).to.throwError() 349 | expect(function () { 350 | preprocStr('//#set _V } //') 351 | }).to.throwError() 352 | }) 353 | 354 | it('evaluating unclosed ES6TL in expression', function () { 355 | expect(function () { 356 | preprocStr('//#set _V ` //') 357 | }).to.throwError() 358 | }) 359 | 360 | }) 361 | 362 | //#endregion Conditional Compilation Errors 363 | -------------------------------------------------------------------------------- /test/s06-replacement.spec.ts: -------------------------------------------------------------------------------- 1 | import { testStr } from './helpers/test-str' 2 | 3 | describe('Code Replacement', function () { 4 | 5 | it('memvars prefixed by "$" can be used for simple code replacement', function () { 6 | testStr([ 7 | '//#set _TRUE true', 8 | '//#set _ONE 1', 9 | '$_TRUE==$_ONE', 10 | '//#set _STR = "OK"', 11 | '"$_STR"', 12 | ], 'true==1\n"OK"') 13 | }) 14 | 15 | it('the prefix "$" is used to paste jscc varname values', function () { 16 | testStr('$_TRUE$_TRUE', 'OKOK', { values: { _TRUE: 'OK' } }) 17 | }) 18 | 19 | it('primitive string or String intance must output its unquoted value', function () { 20 | testStr([ 21 | '$_V1', 22 | '$_V2', 23 | '$_O.v1', 24 | '$_O.v2', 25 | '$_O', 26 | ], [ 27 | 'OK', 28 | 'OK', 29 | 'OK', 30 | 'OK', 31 | '{"v1":"OK","v2":"OK"}', 32 | ].join('\n'), { 33 | // tslint:disable-next-line:no-construct 34 | values: { _V1: 'OK', _V2: new String('OK'), _O: { v1: 'OK', v2: new String('OK') } }, 35 | }) 36 | }) 37 | 38 | it('quotes inside strings must be included in the output', function () { 39 | testStr([ 40 | '$_V1', 41 | '$_V2', 42 | '$_O.v1', 43 | '$_O.v2', 44 | '$_O', 45 | ], [ 46 | '"OK"', 47 | "'OK'", 48 | '"OK"', 49 | "'OK'", 50 | '{"v1":"\\"OK\\"","v2":"\'OK\'"}', 51 | ].join('\n'), { 52 | // tslint:disable-next-line:no-construct 53 | values: { _V1: '"OK"', _V2: new String("'OK'"), _O: { v1: '"OK"', v2: new String("'OK'") } }, 54 | }) 55 | }) 56 | 57 | it('valid dates must output its unquoted JSON value, if alone', function () { 58 | const D = new Date('2018-10-17T00:00:00.0Z').toJSON() 59 | testStr([ 60 | `//#set _V new Date("${D}")`, 61 | `//#set _O {v:new Date("${D}")}`, 62 | '$_V', 63 | '$_O.v', 64 | ], `${D}\n${D}`) 65 | }) 66 | 67 | it('valid dates in JSON objects output has its quoted JSON value', function () { 68 | const D = new Date('2018-10-17T00:00:00.0Z').toJSON() 69 | testStr([ 70 | `//#set _O {v:new Date("${D}")}`, 71 | '$_O', 72 | ], `{"v":"${D}"}`) 73 | }) 74 | 75 | it("invalid dates must outputs 'NaN', if alone", function () { 76 | testStr([ 77 | '//#set _V new Date(NaN)', 78 | `//#set _O {v:new Date(NaN)}`, 79 | '$_V', 80 | '$_O.v', 81 | ], 'NaN\nNaN') 82 | }) 83 | 84 | it('regex objects must output its unquoted `source` value', function () { 85 | const R1 = /\s\\/.source 86 | const R2 = R1.replace(/\\/g, '\\\\') 87 | testStr([ 88 | `//#set _R1 new RegExp("${R2}")`, 89 | `//#set _R2 /${R1}/`, 90 | `//#set _O {r:/${R1}/}`, 91 | '/$_R1/', 92 | '/$_R2/', 93 | '/$_O.r/', 94 | '$_O', 95 | ], 96 | [ 97 | `/${R1}/`, 98 | `/${R1}/`, 99 | `/${R1}/`, 100 | `{"r":"${R2}"}`, 101 | ].join('\n')) 102 | }) 103 | 104 | it('regex must escape embeded double quotes only in JSON objects', function () { 105 | testStr([ 106 | `//#set _R1 /"'/`, 107 | `//#set _O {r:/"'/}`, 108 | '/$_R1/', 109 | '/$_O.r/', 110 | '$_O', 111 | ], 112 | [ 113 | `/"'/`, 114 | `/"'/`, 115 | `{"r":"\\\"'"}`, 116 | ].join('\n')) 117 | }) 118 | 119 | it('Infinity and -Infinity within JSON objects has custom output', function () { 120 | const v1 = JSON.stringify(Number.MAX_VALUE) 121 | const v2 = JSON.stringify(Number.MIN_VALUE) 122 | testStr([ 123 | '//#set _V1 Infinity', 124 | '//#set _V2 new Number(Infinity)', 125 | '//#set _V3 -Infinity', 126 | '//#set _V4 new Number(-Infinity)', 127 | '//#set _O {v1:_V1, v2:_V2, v3:_V3, v4:_V4}', 128 | '$_V1', 129 | '$_V2', 130 | '$_V3', 131 | '$_V4', 132 | '$_O.v1', 133 | '$_O.v2', 134 | '$_O.v3', 135 | '$_O.v4', 136 | '$_O', 137 | ], 138 | [ 139 | 'Infinity', 140 | 'Infinity', 141 | '-Infinity', 142 | '-Infinity', 143 | 'Infinity', 144 | 'Infinity', 145 | '-Infinity', 146 | '-Infinity', 147 | `{"v1":${v1},"v2":${v1},"v3":${v2},"v4":${v2}}`, 148 | ].join('\n')) 149 | }) 150 | 151 | it('primitive `NaN` and Number(NaN) must output an unquoted `NaN`', function () { 152 | testStr([ 153 | '//#set _N NaN', 154 | '//#set _D new Number(NaN)', 155 | '$_N', 156 | '$_D', 157 | ], 'NaN\nNaN') 158 | }) 159 | 160 | it('primitive `NaN` and Number(NaN) within JSON objects must output `null`', function () { 161 | testStr([ 162 | '//#set _O {v1:NaN, v2:new Number(NaN)}', 163 | '$_O', 164 | ], '{"v1":null,"v2":null}') 165 | }) 166 | 167 | it('do not confuse `Infinity` with the string "Infinity"', function () { 168 | testStr([ 169 | '//#set _V1 "Infinity"', 170 | '//#set _V2 new String("Infinity")', 171 | '//#set _V3 "-Infinity"', 172 | '//#set _V4 new String("-Infinity")', 173 | '//#set _O {v1:_V1,v2:_V2,v3:_V3}', 174 | '$_V1', 175 | '$_V2', 176 | '$_V3', 177 | '$_V4', 178 | '$_O.v1', 179 | '$_O.v2', 180 | '$_O.v3', 181 | '$_O', 182 | ], [ 183 | 'Infinity', 184 | 'Infinity', 185 | '-Infinity', 186 | '-Infinity', 187 | 'Infinity', 188 | 'Infinity', 189 | '-Infinity', 190 | '{"v1":"Infinity","v2":"Infinity","v3":"-Infinity"}', 191 | ].join('\n')) 192 | }) 193 | 194 | it('must replace nested object properties (primitive values)', function () { 195 | testStr('$_O.p1.p2.p3', '1', { values: { 196 | _O: { p1: { p2: { p3: 1 } } }, 197 | } }) 198 | }) 199 | 200 | it('must replace nested object properties (object values)', function () { 201 | testStr('$_O.p1.p2', '{"p3":1}', { values: { 202 | _O: { p1: { p2: { p3: 1 } } }, 203 | } }) 204 | }) 205 | 206 | it('must stop on any property with primitive value', function () { 207 | testStr('$_O.p1.ext', 'foo.ext', { values: { 208 | _O: { p1: 'foo' }, 209 | } }) 210 | }) 211 | 212 | it('must replace unexistent properties with `undefined`', function () { 213 | testStr('$_O.p1.p2', 'undefined', { values: { 214 | _O: { p1: {} }, 215 | } }) 216 | testStr('$_O.p1.p2', 'undefined.p2', { values: { 217 | _O: {}, 218 | } }) 219 | testStr('$_O.foo', 'undefined', { values: { 220 | _O: { p1: 1 }, 221 | } }) 222 | }) 223 | 224 | it('must handle quoted values in object properties', function () { 225 | testStr('$_O', '{"s1":"a\\\"s","s2":"a\'s","s3":"a\'\\\"s"}', { values: { 226 | _O: { s1: 'a"s', s2: "a's", s3: 'a\'"s' }, 227 | } }) 228 | }) 229 | 230 | it('must concatenate nested object properties', function () { 231 | testStr('$_O1.p1.p2$_O2.p1.p2', 'V1', { values: { 232 | _O1: { p1: { p2: 'V' } }, 233 | _O2: { p1: { p2: 1 } }, 234 | } }) 235 | }) 236 | 237 | it('must ignore properties that follows primitive values', function () { 238 | testStr('$_O.p.ext', 'V.ext', { values: { 239 | _O: { p: 'V' }, 240 | } }) 241 | }) 242 | 243 | it('must ignore properties enclosed by brackets', function () { 244 | testStr('$_O["p"]', '{"p":"V"}["p"]', { values: { 245 | _O: { p: 'V' }, 246 | } }) 247 | testStr('$_A[0]', '["V"][0]', { values: { 248 | _A: ['V'], 249 | } }) 250 | }) 251 | 252 | it('must replace values in arrays, with dot notation', function () { 253 | testStr('$_A.1', '2', { values: { 254 | _A: [1, 2], 255 | } }) 256 | }) 257 | 258 | it('must replace nested properties of objects in arrays', function () { 259 | testStr('$_A.0.p1.p2', '1', { values: { 260 | _A: [{ p1: { p2: 1 } }], 261 | } }) 262 | }) 263 | 264 | it('must replace nested properties of objects in arrays (alt)', function () { 265 | testStr('$_A.0', '{"p1":{"p2":1}}', { values: { 266 | _A: [{ p1: { p2: 1 } }], 267 | } }) 268 | }) 269 | 270 | it('must handle quoted elements in arrays', function () { 271 | testStr('$_A', '["a\\\"s","a\'s","a\'\\\"s"]', { values: { 272 | _A: ['a"s', "a's", 'a\'"s'], 273 | } }) 274 | }) 275 | 276 | it('must replace the same value multiple times', function () { 277 | testStr('$_A$_A\n$_A$_A', 'ZZ\nZZ', { values: { 278 | _A: 'Z', 279 | } }) 280 | }) 281 | 282 | it('must replace the same value multiple times (arrays)', function () { 283 | testStr('$_A.0$_A.0\n$_A.0$_A.0', 'ZZ\nZZ', { values: { 284 | _A: ['Z'], 285 | } }) 286 | }) 287 | 288 | it('must escape single quotes in strings if `escapeQuotes: "single"`', function () { 289 | testStr('$_S', "str\\'s", { 290 | escapeQuotes: 'single', 291 | values: { _S: "str's" }, 292 | }) 293 | testStr("'$_S'", "'str\\'s'", { 294 | escapeQuotes: 'single', 295 | values: { _S: "str's" }, 296 | }) 297 | testStr('"$_S"', '"str\\\'s"', { 298 | escapeQuotes: 'single', 299 | values: { _S: "str's" }, 300 | }) 301 | }) 302 | 303 | it('must escape double quotes in strings if `escapeQuotes: "double"`', function () { 304 | testStr('$_S', 'str\\\"s', { 305 | escapeQuotes: 'double', 306 | values: { _S: 'str"s' }, 307 | }) 308 | testStr('"$_S"', '"str\\"s"', { 309 | escapeQuotes: 'double', 310 | values: { _S: 'str"s' }, 311 | }) 312 | testStr("'$_S'", "'str\\\"s'", { 313 | escapeQuotes: 'double', 314 | values: { _S: 'str"s' }, 315 | }) 316 | }) 317 | 318 | it('must escape both single and double quotes if `escapeQuotes: "both"`', function () { 319 | testStr('$_S', '\\"str\\\'s\\"', { 320 | escapeQuotes: 'both', 321 | values: { _S: '"str\'s"' }, 322 | }) 323 | }) 324 | 325 | it('must not escape quotes in regexes, even if `escapeQuotes` is used', function () { 326 | testStr([ 327 | `//#set _R1 /"'/`, 328 | `//#set _O {r:/"'/}`, 329 | '/$_R1/', 330 | '/$_O.r/', 331 | '$_O', 332 | ], 333 | [ 334 | `/"'/`, 335 | `/"'/`, 336 | `{"r":"\\\"'"}`, 337 | ].join('\n'), { 338 | escapeQuotes: 'both', 339 | }) 340 | }) 341 | 342 | it('must escape quotes in the output of alone values, if required', function () { 343 | testStr('$_O.s', "str\\'s", { 344 | escapeQuotes: 'single', 345 | values: { _O: { s: "str's" } }, 346 | }) 347 | testStr('$_O.s', 'str\\"s', { 348 | escapeQuotes: 'double', 349 | values: { _O: { s: 'str"s' } }, 350 | }) 351 | testStr('$_O.s', '\\"str\\\'s\\"', { 352 | escapeQuotes: 'both', 353 | values: { _O: { s: '"str\'s"' } }, 354 | }) 355 | }) 356 | 357 | }) 358 | -------------------------------------------------------------------------------- /test/s08-varnames.spec.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import { preprocStr } from './helpers/preproc-str' 3 | import { testStr } from './helpers/test-str' 4 | 5 | describe('Compile-time Variables', function () { 6 | 7 | it('can be defined within the code through `#set`', function () { 8 | testStr([ 9 | '//#set _TRUE 1', 10 | '//#if _TRUE', 11 | 'OK', 12 | '//#endif', 13 | '//#set _STR "Yes"', 14 | '$_STR', 15 | ], 'OK\nYes') 16 | }) 17 | 18 | it('can be defined within the code with JS expressions', function () { 19 | testStr([ 20 | '//#set _EXPR = 0', 21 | '//#if _EXPR', 22 | 'false', 23 | '//#endif', 24 | '//#set _EXPR = !_EXPR', 25 | '//#if _EXPR', 26 | 'OK', 27 | '//#endif', 28 | '//#set _EXPR = "foobar".slice(0,3)', 29 | '//#if _EXPR === "foo"', 30 | 'Yes', 31 | '//#endif', 32 | ], 'OK\nYes') 33 | }) 34 | 35 | it('must default to `undefined` if no value is given', function () { 36 | testStr([ 37 | '//#set _UNDEF', 38 | '//#if _UNDEF === undefined', 39 | 'OK', 40 | '//#endif', 41 | ], 'OK') 42 | }) 43 | 44 | it('unexisting vars are replaced with `undefined` during the evaluation', function () { 45 | testStr([ 46 | '//#ifset _FOO', 47 | 'false', 48 | '//#endif', 49 | '//#if _BAR === undefined', 50 | 'OK', 51 | '//#endif', 52 | ], 'OK') 53 | }) 54 | 55 | it('`#unset` removes defined variables', function () { 56 | testStr([ 57 | '//#ifnset _TRUE', 58 | '"_TRUE unset"', 59 | '//#endif', 60 | '//#unset _TRUE', 61 | '//#ifnset _TRUE', 62 | 'OK', 63 | '//#endif', 64 | ], 'OK', { values: { _TRUE: true } }) 65 | }) 66 | 67 | it('can be changed anywhere in the code', function () { 68 | testStr([ 69 | '//#set _FOO true', 70 | '$_FOO', 71 | '//#set _FOO "OK"', 72 | '"$_FOO"', 73 | '//#unset _FOO', 74 | '$_FOO', 75 | '//#set _FOO 1', 76 | '$_FOO', 77 | ], 'true\n"OK"\n$_FOO\n1') 78 | }) 79 | 80 | it('must recognize varname with no line-ending', function () { 81 | testStr('$_TRUE', /^true$/, { values: { _TRUE: true } }) 82 | }) 83 | 84 | it('non defined vars in directives can take its value from `global`', function () { 85 | (global as any)._GLOBAL = true 86 | testStr('//#set _G=_GLOBAL\n$_G', /true$/) 87 | delete (global as any)._GLOBAL 88 | }) 89 | 90 | it('must recognize nested properties in objects', function () { 91 | testStr([ 92 | '//#set _P=_O.p1.p2.p3+1', 93 | '//#if _P === 2', 94 | 'OK', 95 | '//#endif', 96 | ], 'OK', { values: { 97 | _O: { p1: { p2: { p3: 1 } } }, 98 | } }) 99 | }) 100 | 101 | }) 102 | 103 | describe('Errors in compile-time variables & evaluation', function () { 104 | 105 | it('incorrect memvar names in `#set` throw "Invalid memvar"', function () { 106 | expect(function () { 107 | preprocStr('//#set =_FOO') 108 | }).to.throwError(/Invalid memvar/) 109 | }) 110 | 111 | it('incorrect memvar names in `#unset` throw "Invalid memvar"', function () { 112 | expect(function () { 113 | preprocStr('//#unset FOO') 114 | }).to.throwError(/Invalid memvar/) 115 | }) 116 | 117 | it('non-existing memvars removed with `#unset` does not throw', function () { 118 | expect(preprocStr('//#unset _FOO')).to.be('') 119 | }) 120 | 121 | it('syntax errors in expressions throws during the evaluation', function () { 122 | expect(function () { 123 | preprocStr('//#set _FOO 1+3)') 124 | }).to.throwError(/ in expression /) 125 | }) 126 | 127 | it('other runtime errors throws (like accesing props of `undefined`)', function () { 128 | expect(function () { 129 | preprocStr('//#set _FOO _FOO.foo.bar') 130 | }).to.throwError(/undefined/) 131 | }) 132 | 133 | }) 134 | -------------------------------------------------------------------------------- /test/s10-options.spec.ts: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import jscc from '..' // with this import, we are also testing ESM interop 3 | 4 | // common helpers 5 | import { preprocStr } from './helpers/preproc-str' 6 | import { testStr } from './helpers/test-str' 7 | 8 | const rawJscc = (code: string, opts?: jscc.Options) => jscc(code, '', opts).code 9 | 10 | describe('Options:', function () { 11 | 12 | it('The `.values` option allows you to define custom variables', function () { 13 | testStr([ 14 | '$_ZERO', 15 | '$_MYBOOL', 16 | '$_MYSTRING', 17 | '$_NULL', 18 | '$_UNDEF', 19 | '$_NOT_DEFINED', 20 | ], [ 21 | '0', 22 | 'false', 23 | 'foo', 24 | 'null', 25 | 'undefined', 26 | '$_NOT_DEFINED', 27 | ].join('\n'), { 28 | values: { 29 | _ZERO: 0, 30 | _MYBOOL: false, 31 | _MYSTRING: 'foo', 32 | _NULL: null, 33 | _UNDEF: undefined, 34 | }, 35 | }) 36 | }) 37 | 38 | it('`.keepLines:true` must preserve line-endings (useful w/o sourceMap)', function () { 39 | const source = [ 40 | '//#set _V1 1', 41 | '//#set _V2 2', 42 | '', 43 | '//#if _V1', 44 | 'one', 45 | '//#endif', 46 | '', 47 | ].join('\n') 48 | const expected = [ 49 | '', 50 | '', 51 | '', 52 | '', 53 | 'one', 54 | '', 55 | '', 56 | ].join('\n') 57 | 58 | expect(rawJscc(source, { keepLines: true })).to.be(expected) 59 | }) 60 | 61 | it('`.sourceMap:false` must disable sourceMap creation', function () { 62 | let result = jscc('//#set _A\n$_A') 63 | expect(result.map).to.be.an('object') 64 | 65 | result = jscc('//#set _A\n$_A', '', { sourceMap: false }) 66 | expect(result.map).to.be(undefined) 67 | }) 68 | 69 | it('`.sourceMap:true` must be `null` if the output has no changes', function () { 70 | const source = '// set _A\n$_A' 71 | const result = jscc(source, '', { sourceMap: true }) 72 | 73 | expect(result.code).to.be(source) 74 | expect(result.map).to.be(null) 75 | }) 76 | 77 | it('user provided `.prefixes` must override the predefined ones', function () { 78 | testStr([ 79 | '//~#if 1', 80 | '//#if true', 81 | '//#endif', 82 | '//~#endif', 83 | ], '//#if true\n//#endif', { 84 | prefixes: ['//~'], 85 | }) 86 | }) 87 | 88 | it('`.prefixes` can include regexes in addition to strings', function () { 89 | testStr([ 90 | '//#if 1', 91 | '//-#if 2', 92 | '// #if 3', 93 | 'true', 94 | '// #endif', 95 | '//-#endif', 96 | '//#endif', 97 | ], 'true', { 98 | prefixes: [/\/\/ */, '//-'], 99 | }) 100 | }) 101 | 102 | it('`.prefixes` strings are sanitized before convert them to regex', function () { 103 | testStr([ 104 | '[^a]#set _V1 1', 105 | 'b.#set _V2 1', 106 | '[c]#set _V1 = _V1+_V2', 107 | 'bc#set _V2 = _V1+_V2', 108 | 'b.#set _V1 = _V1 + _V2', 109 | '@$_V1@', 110 | ], /@2@$/, { 111 | prefixes: ['[^a]', 'b.'], 112 | }) 113 | }) 114 | 115 | it('`.prefixes` must accept one only string', function () { 116 | testStr([ 117 | '//-#if 0', 118 | 'ups', 119 | '//-#endif', 120 | ], '', { 121 | prefixes: '//-', 122 | }) 123 | }) 124 | 125 | it('`.prefixes` must accept one only regex', function () { 126 | testStr([ 127 | '//-#if 0', 128 | 'ups', 129 | '//-#endif', 130 | ], '', { 131 | prefixes: /\/\/-/, 132 | }) 133 | }) 134 | 135 | describe('Errors in options', function () { 136 | 137 | it('incorrect memvar names in `.values` must throw "Invalid memvar"', function () { 138 | expect(function () { 139 | preprocStr('foo()', { values: { FOO: 1 } }) 140 | }).to.throwError(/Invalid memvar/) 141 | }) 142 | 143 | it('non object `.values` must throw "`values` must be a plain object"', function () { 144 | expect(function () { 145 | // @ts-ignore intentional error 146 | preprocStr('foo()', { values: true }) 147 | }).to.throwError(/values must be a plain object/) 148 | }) 149 | 150 | it('`.prefixes` must be a string, regex, or array', function () { 151 | expect(function () { 152 | // @ts-ignore intentional error 153 | preprocStr('foo()', { prefixes: 1 }) 154 | }).to.throwError(/`prefixes` must be a/) 155 | }) 156 | 157 | it('`.prefixes` as array must contain only string or regexes', function () { 158 | expect(function () { 159 | // @ts-ignore intentional error 160 | preprocStr('foo()', { prefixes: ['', /\s/, 1] }) 161 | }).to.throwError(/`prefixes` must be a/) 162 | }) 163 | 164 | }) 165 | 166 | }) 167 | -------------------------------------------------------------------------------- /test/s12.examples.spec.ts: -------------------------------------------------------------------------------- 1 | import { testFile } from './helpers/test-file' 2 | import { testFileStr } from './helpers/test-file-str' 3 | import { testStr } from './helpers/test-str' 4 | 5 | describe('Examples:', function () { 6 | 7 | it('Simple replacement', function () { 8 | testFile('ex-simple-replacement') 9 | }) 10 | 11 | it('Object and properties', function () { 12 | testFile('ex-object-properties') 13 | }) 14 | 15 | it('Using _FILE and dates', function () { 16 | testFileStr('ex-file-and-date', 17 | /ex-file-and-date\.js\s+Date: 20\d{2}-\d{2}-\d{2}\n/) 18 | }) 19 | 20 | it('Hidden blocks (and process.env.*)', function () { 21 | testStr([ 22 | '/*#if 1', 23 | 'import mylib from "browser-lib"', 24 | '//#else //*/', 25 | 'import mylib from "node-lib"', 26 | '//#endif', 27 | 'mylib()', 28 | ], 'import mylib from "browser-lib"\nmylib()') 29 | }) 30 | 31 | it('Changing prefixes to work with CoffeScript', function () { 32 | testStr([ 33 | '# #set _DEBUG true', 34 | '', 35 | '### #if _DEBUG', 36 | 'console.log "debug mode"', 37 | '### #else', 38 | 'console.log "production"', 39 | '# #endif', 40 | ], '\nconsole.log "debug mode"', { 41 | prefixes: ['# ', '### '], 42 | }) 43 | }) 44 | 45 | it('Workaround to #3: not work with eslint rule: comma-spacing', function () { 46 | testFile('eslint-autofix', { 47 | prefixes: [/\/\/ ?/, /\/\* ?/], 48 | }) 49 | }) 50 | 51 | }) 52 | -------------------------------------------------------------------------------- /test/s14-non-js.spec.ts: -------------------------------------------------------------------------------- 1 | import { testFile } from './helpers/test-file' 2 | 3 | describe('HTML Processing', function () { 4 | 5 | it('must work since jscc is language agnostic', function () { 6 | testFile('html-vars-js.html', { 7 | values: { _TITLE: 'My App' }, 8 | }) 9 | }) 10 | 11 | it('must handle html comments ("