Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

Commit

Permalink
added line continuation
Browse files Browse the repository at this point in the history
  • Loading branch information
Pyrolistical committed May 1, 2018
1 parent bc52419 commit b34bcbc
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 31 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules/
specification-input.md
specification-report.md
test/specification-input.md
test/specification-report.md
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
4.0.0 (May 1, 2018)
* added line continuation

3.0.0 (April 28, 2018)
* fixed issue where variable declartion would affect previous block when using promises

Expand Down
39 changes: 21 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const RemarkParse = require('remark-parse')
const RemarkStringify = require('remark-stringify')
const Minimist = require('minimist')

const parser = require('./parser')

const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)

Expand Down Expand Up @@ -58,14 +60,15 @@ function SpecCheck ({requires, errors}) {
if (!(node.type === 'code' && ['js', 'javascript'].includes(node.lang))) {
return [node]
}
const lines = node.value.split('\n')
const rawLines = node.value.split('\n')
let input
let result
let expected
try {
const lines = parser(rawLines)
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('> ')) {
input = lines[i].substr(2)
if (lines[i].type === 'input') {
input = lines[i].code
try {
result = scopedEval(input)
} catch (error) {
Expand All @@ -77,7 +80,7 @@ function SpecCheck ({requires, errors}) {
}
const output = lines[i]
try {
if (output.startsWith('Resolve: ')) {
if (output.type === 'resolve') {
try {
result = {
Resolve: await result
Expand All @@ -88,9 +91,9 @@ function SpecCheck ({requires, errors}) {
}
}
expected = {
Resolve: scopedEval(output.substring('Resolve: '.length))
Resolve: scopedEval(output.code)
}
} else if (output.startsWith('Reject: ')) {
} else if (output.type === 'reject') {
try {
result = {
Resolve: await result
Expand All @@ -101,37 +104,37 @@ function SpecCheck ({requires, errors}) {
}
}
expected = {
Reject: scopedEval(output.substring('Reject: '.length))
Reject: scopedEval(output.code)
}
} else if (output.startsWith('Reject error: ')) {
} else if (output.type === 'reject error') {
try {
result = {
Resolve: await result
}
} catch (error) {
result = `Reject error: ${error.message}`
}
expected = output
} else if (output.startsWith('Reject error code: ')) {
expected = `Reject error: ${output.message}`
} else if (output.type === 'reject error code') {
try {
result = {
Resolve: await result
}
} catch (error) {
result = `Reject error code: ${error.code}`
result = `Reject error code: ${error.errorCode}`
}
expected = output
} else if (output.startsWith('Error: ')) {
expected = `Reject error code: ${output.code}`
} else if (output.type === 'error') {
result = `Error: ${result.message}`
expected = output
} else if (output.startsWith('Error code: ')) {
expected = `Error: ${output.message}`
} else if (output.type === 'error code') {
result = `Error code: ${result.code}`
expected = output
expected = `Error code: ${output.errorCode}`
} else {
expected = scopedEval(output)
expected = scopedEval(output.code)
}
} catch (error) {
throw new Error(`failed to eval output \`${output}\`: ${error.message}`)
throw new Error(`failed to eval output \`${lines[i].code}\`: ${error.message}`)
}
try {
assert.deepEqual(result, expected)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "spec-check",
"version": "3.0.0",
"version": "4.0.0",
"description": "Executable specification in Markdown for Javascript",
"main": "index.js",
"bin": {
"spec-check": "./index.js"
},
"scripts": {
"lint": "eslint --fix .",
"pretest": "rimraf specification-input.md specification-report.md",
"pretest": "rimraf test/specification-input.md test/specification-report.md",
"test": "node test/index.js"
},
"keywords": [
Expand Down
56 changes: 56 additions & 0 deletions parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module.exports = (lines) => {
const result = []
lines.forEach((line) => {
if (line.startsWith('> ')) {
result.push({
type: 'input',
code: line.substring('> '.length)
})
} else if (line.startsWith('Resolve: ')) {
result.push({
type: 'resolve',
code: line.substring('Resolve: '.length)
})
} else if (line.startsWith('Reject: ')) {
result.push({
type: 'reject',
code: line.substring('Reject: '.length)
})
} else if (line.startsWith('Reject error: ')) {
result.push({
type: 'reject error',
message: line.substring('Reject error: '.length)
})
} else if (line.startsWith('Reject error code: ')) {
result.push({
type: 'reject error code',
errorCode: line.substring('Reject error code: '.length)
})
} else if (line.startsWith('Error: ')) {
result.push({
type: 'error',
message: line.substring('Error: '.length)
})
} else if (line.startsWith('Error code: ')) {
result.push({
type: 'error code',
errorCode: line.substring('Error code: '.length)
})
} else if (line.startsWith('... ')) {
if (result.length === 0) {
throw new Error('Continuation must be preceded input or expected output. Cannot start with a continuation.')
}
const {type, code} = result[result.length - 1]
if (['reject error', 'reject error code', 'error', 'error code'].includes(type)) {
throw new Error(`Continuation is not supported for ${type}.`)
}
result[result.length - 1].code = `${code.trim()} ${line.substring('... '.length).trim()}`
} else {
result.push({
type: 'output',
code: line
})
}
})
return result
}
81 changes: 78 additions & 3 deletions specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,56 @@ This is a `spec-check` report which is also an executable specification for `spe

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### `>` can be continued with `...`

```js
> 'Hello
... world'
'Hello world'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Continuations collapses all whitespace to a single space

```js
> 'Hello
... world'
'Hello world'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### There can be multiple continuations

```js
> 'Hell
... o
... world'
'Hell o world'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### A block starting with a continuation errors

```js
... 42
42
```

`Error: Continuation must be preceded input or expected output. Cannot start with a continuation.`

### Expected output can be continued with `...`

```js
> 'Hello world'
'Hello
... world'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Promise that resolve can be checked

```js
Expand All @@ -33,6 +83,16 @@ Resolve: 'Hello'

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Promise that resolve can be continued with `...`

```js
> Promise.resolve('Hello world')
Resolve: 'Hello
... world'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Promise that reject a value can be checked

```js
Expand All @@ -42,6 +102,15 @@ Reject: 'oops'

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Promise that reject a value can be continued with `...`

```js
> Promise.reject('oops')
Reject: 'oops'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### Promise that are expected to resolve, but are rejected error

```js
Expand Down Expand Up @@ -132,7 +201,7 @@ Error code: ERROR_OOPS
### errors thrown by output lines are rethrown

```js
> 'everything is fine'
> 'Everything is fine'
throw new Error('oops')
```

Expand All @@ -152,14 +221,20 @@ throw new Error('oops')

```js
> throw new Error('oops')
> 'Everythign is fine.'
'Everythign is fine.'
> 'Everything is fine.'
'Everything is fine.'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

### each block is not isolated from each other, be careful

```js
> name = 'Bob'
```

[`spec-check`](https://github.com/concept-not-found/spec-check)ed

```js
> name
undefined
Expand Down
20 changes: 15 additions & 5 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,29 @@ async function assertSpecification () {
.use(RemoveLinesAddedBySpecCheckReport)
.use(RemarkStringify)
.process(expected)
await writeFile('specification-input.md', input)
await writeFile('test/specification-input.md', input)

const exitCode = await runSpecCheck(
'--report',
'specification-report.md',
'test/specification-report.md',
'add=test/add.js',
'specification-input.md'
'test/specification-input.md'
)
if (exitCode !== 0) {
throw new Error(`expected --report option to always exit 0, but was ${exitCode}`)
}
const result = await readFile('specification-report.md', 'utf8')
const result = await readFile('test/specification-report.md', 'utf8')
if (expected !== result) {
throw new Error('expected specification.md to be the same as specification-report.md')
throw new Error('expected specification.md to be the same as test/specification-report.md')
}
}

async function assertParser () {
const exitCode = await runSpecCheck(
'test/parser.spec.md'
)
if (exitCode !== 0) {
throw new Error(`expected exit 0, but was ${exitCode}`)
}
}

Expand All @@ -81,6 +90,7 @@ async function assertMultipleErrorsTest () {
}

async function main () {
await assertParser()
await assertSpecification()
await assertNoErrorsTest()
await assertMultipleErrorsTest()
Expand Down
Loading

0 comments on commit b34bcbc

Please sign in to comment.