├── .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 ("