Skip to content

Commit

Permalink
feat(types): add types of expressions, close #28
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov committed Feb 14, 2017
1 parent 9d1214d commit dc329f3
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 17 deletions.
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -139,6 +139,20 @@ R.compose(
)(-4) //=> 7
```

## Mixing values and types

You can record either values or types of values

```js
var R = require('ramda')
R.compose(
Math.abs, //:: number
R.add(1), //=> -7
R.multiply(2) //=> -8
)(-4)
// :: number
```

## Console log statements

If the value comment is on the left of `console.log(value)` expression,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -80,7 +80,7 @@
"pretest": "npm run lint",
"secure": "nsp check",
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
"unit": "mocha src/*-spec.js test/**/spec.js",
"unit": "mocha src/*-spec.js test/spec.js test/**/spec.js",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"test": "DEBUG=comment-value instrumented=1 npm run instrument && npm start && cat results.json && npm run update",
"posttest": "npm run unit",
Expand Down
23 changes: 15 additions & 8 deletions src/instrument-source.js
Expand Up @@ -17,22 +17,25 @@ if (!global.instrument) {
}
const emitter = global.instrument

function storeInIIFE (reference, value) {
function storeInIIFE (reference, value, typeReference) {
return `(function () {
if (typeof ${value} === 'function') {
return function () {
${reference} = ${value}.apply(null, arguments)
${reference} = ${value}.apply(null, arguments);
${typeReference} = typeof ${reference};
return ${reference}
}
} else {
${reference} = ${value};
${typeReference} = typeof ${reference};
return ${reference}
}
}())`
}
function storeInBlock (reference, value) {
const store = reference + ' = ' + value
return `;{ ${store}; ${reference} }`
function storeInBlock (reference, value, typeReference) {
const store = `${reference} = ${value}`
const storeType = `${typeReference} = typeof ${reference}`
return `;{ ${store}; ${storeType}; ${reference} }`
}

function instrumentSource (source, filename) {
Expand Down Expand Up @@ -117,6 +120,7 @@ function instrumentSource (source, filename) {
debug('will instrument "%s" for comment "%s"', node.source(), comment.text)
comment.instrumented = true
const reference = 'global.__instrumenter.comments[' + comment.index + '].value'
const typeReference = 'global.__instrumenter.comments[' + comment.index + '].type'
if (isConsoleLogExpression(node)) {
debug('instrumenting console.log', node.source())
// instrument inside the console.log (the first argument)
Expand All @@ -137,18 +141,21 @@ function instrumentSource (source, filename) {

let storeAndReturn
if (node.parent.type === 'CallExpression') {
storeAndReturn = storeInIIFE(reference, value)
storeAndReturn = storeInIIFE(reference, value, typeReference)
emitter.emit('wrap', value)
emitter.emit('wrap-node', {type: 'CallExpression', value})
} else if (node.parent.type === 'MemberExpression') {
// update the entire parent node
const value = node.parent.source()
emitter.emit('wrap', value)
let parentStore = storeInIIFE(reference, value)
emitter.emit('wrap-node', {type: 'MemberExpression', value})
let parentStore = storeInIIFE(reference, value, typeReference)
node.parent.update(parentStore)
return
} else {
emitter.emit('wrap', value)
storeAndReturn = storeInBlock(reference, value)
emitter.emit('wrap-node', {type: node.parent.type, value})
storeAndReturn = storeInBlock(reference, value, typeReference)
}

if (node.parent.parent &&
Expand Down
5 changes: 4 additions & 1 deletion src/update.js
Expand Up @@ -68,7 +68,10 @@ function updateFile (filename, results) {
// console.log('updating line', line, 'with value', c.value)
const k = line.indexOf(commentStart)
la(k >= 0, 'line does not have comment', k, 'for comment', c)
const newComment = commentStart + ' ' + JSON.stringify(c.value)

const value = c.find === 'value' ? JSON.stringify(c.value) : c.type

const newComment = commentStart + ' ' + value
const updatedLine = line.substr(0, k) + newComment
lines[c.from.line - 1] = updatedLine
}
Expand Down
49 changes: 49 additions & 0 deletions test/spec.js
@@ -0,0 +1,49 @@
const la = require('lazy-ass')
const is = require('check-more-types')
const instrument = require('../src/instrument-source')
const R = require('ramda')

describe.only('composed types', () => {
const source = `
const R = require('ramda')
const pipe = R.pipe(
R.inc, //::
R.multiply(10) //>
)
pipe(-4)
`
let emitter

beforeEach(() => {
emitter = global.instrument
delete global.__instrumenter
})

it('finds the special comments', () => {
const starts = []
emitter.on('comment', c => starts.push(c.commentStart))
instrument(source)
la(R.equals(['::', '>'], starts), starts)
})

it('evaluates original source', () => {
const result = eval(source)
la(result === -30, result)
})

it('evaluates instrumented source', () => {
const s = instrument(source)
const result = eval(s)
la(result === -30, result)
la(global.__instrumenter.comments.length === 2,
global.__instrumenter.comments)
})

it('keeps type for first comment', () => {
const s = instrument(source)
const result = eval(s)
const first = global.__instrumenter.comments[0]
la(first.find === 'type', 'should find type', first)
la(first.type === 'number', 'found type', first)
})
})
9 changes: 3 additions & 6 deletions test/types2/expected.js
@@ -1,6 +1,3 @@
// set type of any variable
// by using the "// name::" syntax
const foo = 'f' + 'o' + 'o'
// foo:: "string"
const life = 42
// life:: "number"
// type of preceding expression
const add = (a, b) => a + b
add(2, 3) // :: number
2 changes: 1 addition & 1 deletion test/types2/index.js
@@ -1,3 +1,3 @@
// type of preceding expression
const add = (a, b) => a + b
add(2, 3) // :: 5
add(2, 3) // :: number
14 changes: 14 additions & 0 deletions test/types2/spec.js
Expand Up @@ -19,4 +19,18 @@ describe('expression type', () => {
instrument(source)
la(R.equals([' ::'], starts), starts)
})

it('wraps the right expressions', () => {
const wraps = []
emitter.on('wrap', s => wraps.push(s))
instrument(source)
la(R.equals(['add(2, 3)'], wraps), wraps)
})

it('wraps the right AST node type', () => {
const nodeTypes = []
emitter.on('wrap-node', s => nodeTypes.push(s.type))
instrument(source)
la(R.equals(['ExpressionStatement'], nodeTypes), nodeTypes)
})
})

0 comments on commit dc329f3

Please sign in to comment.