Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
aMarCruz committed Nov 23, 2018
1 parent 11fa7e3 commit de1a779
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 118 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

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).

## \[1.1.0] - 2018-11-21
## \[1.1.0] - 2018-11-22

### Added

- Option `escapeQuotes` to escape quotes in the output of strings (not wrapped by JSON output).
- TSLint instead of ESLint, for compatibility with CI services.
- Codacy quality and coverage services.
- [Codacy](https://api.codacy.com) quality and coverage services.

### Changed

- Convert `export.default` to `module.exports` in internal modules. Since it is a node.js library, it looks right and produces a cleaner code.
- The output of chained properties stops with a primitive value, to avoid some compile-time errors.
- Updated Readme, add "vulnerabilities" badge from [snyk.io](https://snyk.io).
- Regression of the replacement of `NaN` with `null` since the later alters the behavior of the Date ctor.
- Simplify the `parseChunk` function, logic moved to the `parseHelper` class.

### Removed

Expand Down
66 changes: 36 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# jscc

[![npm][npm-image]][npm-url]
[![License][license-image]][license-url]
[![AppVeyor][appveypr-image]][appveypr-url]
[![Build Status][travis-image]][travis-url]
[![Codebeat][codebeat-image]][codebeat-url]
[![Coverage][codecov-image]][codecov-url]
[![jscc on npm][npm-badge]][npm-url]
[![Windows Build][appveypr-badge]][appveypr-url]
[![Linux Build][travis-badge]][travis-url]
[![Codebeat][codebeat-badge]][codebeat-url]
[![Coverage][codecov-badge]][codecov-url]
[![Vulnerabilities][snyk-badge]][snyk-url]
[![License MIT][license-badge]][license-url]

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.

Expand Down Expand Up @@ -68,6 +69,8 @@ The result is a plain JS object with a property `code`, a string with the proces

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.

Please see the Wiki to know the supported [options](https://github.com/aMarCruz/jscc/wiki/Options).

## Directives

jscc works with _directives_ inserted in the text files and prefixed with configurable character sequences, that defaults to `'/*'`, `'//'` and `'<!--'`.
Expand Down Expand Up @@ -114,28 +117,28 @@ This is the opposite to `#ifset`, it returns `false` if the `varname` does not e

The behavior of `#elif` is similar to the JS `else if` construction.

The `expression` will be evaluated if the previous `#if` or `#elif` was falsy.
The `expression` will be evaluated if the previous `#if` or `#elif` was _falsy_.

You can have zero or more `#elif` directives following one `#if`.

### **`#endif`**

Closes the previous conditional block.
Closes the current conditional block.

### **`#error <expression>`**

It evaluates the `expression` of characters and with its result generates an exception at compile time.
Generates an exception at compile time with the result of the character `expression` provided.

You can read in the Wiki about:
You can learn more about this in the Wiki:

- [Options](https://github.com/aMarCruz/jscc/wiki/Options)
- [Basic Syntax](https://github.com/aMarCruz/jscc/wiki/Syntax)
- [Examples & Tricks](https://github.com/aMarCruz/jscc/wiki/Examples)

## Changes in This Version

- New `escapeQuotes` option, to escape quotes in the output of strings (not wrapped by JSON output).
- The output of chained properties stops with a primitive value, to avoid some compile-time errors.
- Regression of the replacement of `NaN` with `null` since the later alters the behavior of the Date ctor.
- Removed the ESM export, CommonJS in ES6 is the only exported format.
- ESLint replaced with TSLint, for compatibility with CI services.
- Added Codacy quality test, removed Coverity.
Expand All @@ -159,48 +162,51 @@ For important changes in v1.0.0, please see the [Changelog](CHANGELOG.md).

## Support my Work

I'm a full-stack developer with more than 20 year of experience and I try to share most of my work for free and help others, but this takes a significant amount of time and effort so, if you like my work, please consider...
I'm a full-stack developer with more than 20 year of experience and I try to share most of my work for free and help others, but this takes a significant amount of time, effort and coffee so, if you like my work, please consider...

[![Buy Me a Coffee][bmc-image]][bmc-url]
[<img src="https://amarcruz.github.io/images/kofi_blue.png" height="36" title="Support Me on Ko-fi" />][kofi-url]

Of course, feedback, PRs, and stars are also welcome :)
Of course, feedback, PRs, and stars are also welcome 🙃

Thanks for your support!

## License

The [MIT](LICENSE) License.

Copyright (c) 2018, Alberto Martínez.
&copy; 2018, Alberto Martínez

---

[![Codacy][codacy-badge]][codacy-url]
[![Codacy Coverage][codacyc-badge]][codacyc-url]
[![CodeClimate][climate-image]][climate-url]
[![CodeClimate Coverage][climatec-image]][climatec-url]
[![Issues][issues-image]][issues-url]
[![CodeClimate][climate-badge]][climate-url]
[![CodeClimate Coverage][climatec-badge]][climatec-url]
[![Issues][issues-badge]][issues-url]

<!-- Badges -->
[npm-image]: https://img.shields.io/npm/v/jscc.svg
[npm-badge]: https://img.shields.io/npm/v/jscc.svg
[npm-url]: https://www.npmjs.com/package/jscc
[license-image]: https://img.shields.io/github/license/mashape/apistatus.svg
[license-badge]: https://img.shields.io/github/license/mashape/apistatus.svg
[license-url]: https://github.com/aMarCruz/jscc/blob/master/LICENSE
[appveypr-image]: https://ci.appveyor.com/api/projects/status/hdsef0p6q0oqr127?svg=true
[appveypr-badge]: https://ci.appveyor.com/api/projects/status/hdsef0p6q0oqr127?svg=true
[appveypr-url]: https://ci.appveyor.com/project/aMarCruz/jscc
[travis-image]: https://img.shields.io/travis/aMarCruz/jscc.svg
[travis-badge]: https://img.shields.io/travis/aMarCruz/jscc.svg
[travis-url]: https://travis-ci.org/aMarCruz/jscc
[snyk-badge]: https://snyk.io/test/github/aMarCruz/jscc/badge.svg?targetFile=package.json
[snyk-url]: https://snyk.io/test/github/aMarCruz/jscc?targetFile=package.json
[codacy-badge]: https://api.codacy.com/project/badge/Grade/30e8679fcd614227837ad250dd6c4030
[codacy-url]: https://www.codacy.com/app/aMarCruz/jscc?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=aMarCruz/jscc&amp;utm_campaign=Badge_Grade
[codacyc-badge]: https://api.codacy.com/project/badge/Coverage/30e8679fcd614227837ad250dd6c4030
[codacyc-url]: https://www.codacy.com/app/aMarCruz/jscc?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=aMarCruz/jscc&amp;utm_campaign=Badge_Coverage
[codebeat-image]: https://codebeat.co/badges/7e15dc9d-42a8-4ea2-8bae-a21c09490fbe
[codebeat-badge]: https://codebeat.co/badges/7e15dc9d-42a8-4ea2-8bae-a21c09490fbe
[codebeat-url]: https://codebeat.co/projects/github-com-amarcruz-jscc-dev
[codecov-image]: https://codecov.io/gh/aMarCruz/jscc/branch/dev/graph/badge.svg
[codecov-badge]: https://codecov.io/gh/aMarCruz/jscc/branch/dev/graph/badge.svg
[codecov-url]: https://codecov.io/gh/aMarCruz/jscc
[climate-image]: https://codeclimate.com/github/aMarCruz/jscc/badges/gpa.svg
[climate-badge]: https://codeclimate.com/github/aMarCruz/jscc/badges/gpa.svg
[climate-url]: https://codeclimate.com/github/aMarCruz/jscc
[issues-image]: https://codeclimate.com/github/aMarCruz/jscc/badges/issue_count.svg
[issues-badge]: https://codeclimate.com/github/aMarCruz/jscc/badges/issue_count.svg
[issues-url]: https://codeclimate.com/github/aMarCruz/jscc
[climatec-image]: https://api.codeclimate.com/v1/badges/50d60a10ec7c9156b429/test_coverage
[climatec-url]: https://codeclimate.com/github/aMarCruz/jscc/test_coverage
[bmc-image]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png
[bmc-url]: https://www.buymeacoffee.com/aMarCruz
[climatec-badge]: https://api.codeclimate.com/v1/badges/50d60a10ec7c9156b429/test_coverage
[climatec-url]: https://codeclimate.com/github/aMarCruz/jscc/test_coverage
[kofi-url]: https://ko-fi.com/C0C7LF7I
66 changes: 31 additions & 35 deletions src/parse-chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import ParseHelper = require('./parse-helper')
import Parser = require('./parser')

/**
* Workaround for possible error with a BOM mark in the source.
* Handles possible error with a BOM mark in the source by replacing it with
* an EOL (it allows the parser regex identify the start of the first line).
*
* The mark is preserved because, if there are replacements, the edited buffer
* will be obtained from magicStr, which will not touch the mark, and if there
* are not, the caller will use the original source.
*
* @param source The original source
*/
const withoutBOMmark = (source: string) => {
return source.charCodeAt(0) === 0xFEFF ? '\n' + source.slice(1) : source
Expand All @@ -14,58 +21,47 @@ const withoutBOMmark = (source: string) => {
* For each match found, calls the parser with the result of the regex and
* the parser returns the next position from which to continue the search.
*
* @param parser Parser instance to use
* @param parser jscc parser instance
* @param source The original source
* @param helper Functions to flush and remove chuncks
*/
const parseChunks = function _parseChunks (parser: Parser, source: string, helper: ParseHelper) {

let hideStart = 0 // keep the start position of the block to hide
let lastIndex = 0 // keep the position of the next chunk to parse
let output = true

const re = parser.getRegex() // $1:keyword, $2:expression
// Get a regex from the jscc parser to match line containing directives.
// This regex depends on the prefixes in use and its match is handled by
// the jscc parser, here we only care about the position of the matched line.
const re = parser.getRegex()

let match = re.exec(withoutBOMmark(source))
const changes = !!match // avoid send sourceMap if there's no changes

while (match) {
const index = match.index
// With `re`, there's no way for a line other than a directive to be
// matched, so we can set a flag here to avoid a non-necessary sourcemap.
const changes = !!match

// Replace varnames in the current chunk and flush it, if necessary.
helper.commit(lastIndex, index, output)

if (output !== parser.parse(match)) {
// Output state changed

if (output) {
// The output ends, save the position where this new
// hidden block begins.
hideStart = index
}

// Else, the output begins and the hidden block will be removed.
// (hasOutput is initialized with `true`, so a hidden block exists)
output = !output
while (match) {

} else if (output) {
// The output state has not changed and the output is enabled,
// will remove the line of the processed directive.
hideStart = index
// The parser could change the jscc varnames, so it's necessary
// to replace any pending chunks before parsing the line.
helper.flushPrev(match.index)

// Otherwise, it will be removed together with the current hidden
// block when this ends.
}
// Parse the line and update buffers and searching position.
// `parser.parse` returns the new output state.
re.lastIndex = helper.flushLine(
match.index,
re.lastIndex,
parser.parse(match)
)

lastIndex = re.lastIndex = helper.remove(hideStart, re.lastIndex, output)
// With lastIndex already updated, search the next directive.
match = re.exec(source)
}

// This will throw if the buffer has unbalanced blocks
parser.close()

// This final flush is necessary, don't delete it
return helper.commit(lastIndex, source.length, true) || changes
// This final flush is necessary because the source can have replacements
// even if it does not contain directives.
return helper.flush() || changes
}

export = parseChunks
103 changes: 84 additions & 19 deletions src/parse-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,84 @@ const EOLS = /[^\r\n]+/g
*/
class ParseHelper {

private lastPos = 0 // keep the next offset to flush
private output = true // output state, starts "active"

/**
* @param source Original source
* @param props jscc properties
*/
constructor (private source: string, private props: JsccProps) {
}

/**
* Final flush. The final output is always in "active" state.
*/
public flush () {
return this.commit(this.lastPos, this.source.length)
}

/**
* Write pending changes.
*
* _IMPORTANT:_ `Parser.parse` can change the current jscc values,
* so this function _MUST BE_ called before the parsing to make any
* replacements with the current values.
*
* @param offset Starting position of the matched line
*/
public flushPrev (offset: number) {
if (this.output) {
this.commit(this.lastPos, offset)
}
}

/**
* A line was processed, flush buffers as necessary.
*
* @param start Starting position of the chunk into the original buffer
* @param end Position of the character following the chunk
* @param output The updated output state
* @returns The updated position where to continue the search.
*/
public flushLine (start: number, end: number, output: boolean) {

// Find the start of the next line in the buffer.
if (end < this.source.length) {
end += this.source.substr(end, 2) === '\r\n' ? 2 : 1
}

if (output !== this.output) {
this.output = output
this.flushit(start, end)

} else if (output) {
// flushPrev was already called, so no need to commit
this.remove(start, end)
}

return end
}

/**
* If the parsed `chunk` seems to contain varnames to replace, call the
* remapVars function which will make the replacement and store the chunk
* into the MagicString intance. Otherwise, do nothing.
*
* _NOTE:_ This function updates `this.lastPos`
*
* @param start Starting position of the chunk into the original buffer
* @param end Ending position (the character followng the chunk)
* @param output Must replace the block now?
* @param end Position of the character following the chunk
* @returns `true` if the chunk was changed
*/
public commit (start: number, end: number, output: boolean) {
private commit (start: number, end: number) {

if (!output || start >= end) {
if (start >= end) {
return false
}

this.lastPos = end

// Get the fragment of source where to search varnames to replace
const chunk = this.source.slice(start, end)

Expand All @@ -41,29 +96,39 @@ class ParseHelper {
}

/**
* Removes the block from the `start` to the `end` position, inclusive, plus
* the following line-ending (one char for mac/unix, two for windows type).
* Removes the block from the `start` to the `end` position, inclusive.
*
* _NOTE:_ This function updates `this.lastPos`
*
* @param start Starting position of the chunk into the original buffer
* @param end Ending position (the character followng the chunk)
* @param output Must remove the block now?
* @param end Position of the character following the chunk
* @returns Position of the character following the removed block.
*/
public remove (start: number, end: number, output: boolean) {
private remove (start: number, end: number) {

if (end < this.source.length) {
end += this.source[end] === '\r' && this.source[end + 1] === '\n' ? 2 : 1
}
this.lastPos = end

// Only do the replacement if the output is enabled
if (output) {
const block = this.props.keepLines
? this.source.slice(start, end).replace(EOLS, '') : ''
const block = this.props.keepLines
? this.source.slice(start, end).replace(EOLS, '') : ''

this.props.magicStr.overwrite(start, end, block)
}
this.props.magicStr.overwrite(start, end, block)
}

return end
/**
* The output state changed, flush the buffer.
*
* @param start Start of current line
* @param end End of current line
*/
private flushit (start: number, end: number) {
if (this.output) {
// Output begins, remove previous hidden block.
this.remove(this.lastPos, end)

} else {
// Output ends, flush the already processed block.
this.commit(this.lastPos, start)
}
}
}

Expand Down

0 comments on commit de1a779

Please sign in to comment.