Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] power-assert v2 #27

Open
wants to merge 115 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
b95c5da
feat: first PoC implementation of power-assert-2 transpiler
twada Jul 30, 2018
541a901
test: add tests and jsdoc for ArgumentRecorder
twada Aug 2, 2018
8f6f281
chore: set default Promise wrapper status as "pending"
twada Aug 3, 2018
33b6f7e
chore(empower-core): PoC implementation of power-assert-2 function re…
twada Aug 6, 2018
7da014f
chore: pass callee of assertion to recorder to enable runtime feature…
twada Aug 6, 2018
2324da5
chore: PoC of assertion recorder with assignment
twada Aug 7, 2018
415c95a
docs: adding JSDoc comments and type declarations
twada Aug 7, 2018
b477fd8
refactor: rename variable
twada Aug 7, 2018
0d665aa
refactor: separate assignment from node creation
twada Aug 7, 2018
d3404ee
feat: put AssertionRecorder in place of assertion message argument
twada Aug 7, 2018
7e0bf25
feat: return ArgumentRecorder itself since we have callee for runtime…
twada Aug 8, 2018
693e05f
feat: pass AssertionMetadata to ArgumentRecorder so now runtime side …
twada Aug 9, 2018
ea87680
refactor: AssertionRecorder also acts like ArgumentRecorder to simpli…
twada Aug 9, 2018
4a4af2e
refactor: rename AssertionRecorder to AssertionMessage
twada Aug 9, 2018
b77118f
chore: shorten generated name a bit
twada Aug 9, 2018
84a342a
refactor: generalize optional message handling
twada Aug 9, 2018
aecab0d
fix: use currentNode.arguments.length to calculate number of actual a…
twada Aug 9, 2018
4675ab8
feat: enhance message even if it is a string literal
twada Aug 9, 2018
4a10799
feat: support assert.rejects and assert.doesNotReject
twada Aug 10, 2018
8b23eb8
feat: support `assert.throws`, `assert.doesNotThrow`
twada Aug 23, 2018
c0277f7
chore: update call-matcher to ^1.1.0
twada Aug 23, 2018
61ae79c
refactor: reduce number of matching attempts
twada Aug 23, 2018
1d84086
feat: proxy functions if and only if it is a block argument
twada Aug 23, 2018
6c69ec0
refactor: separate argument capturing from intermediate Node capturing
twada Aug 24, 2018
8b2c3c2
refactor: embed argument metadata to move block handling
twada Aug 25, 2018
2b0a87b
feat: embed config object into code to give more hint to runtime side
twada Nov 21, 2018
7ccd6ed
chore: fix delegation
twada Nov 21, 2018
714e561
fix: dealing with undefined callee (there's no such method)
twada Nov 22, 2018
b0bd7f9
test: update fixtures
twada Nov 23, 2018
ba6cce7
chore: sync with latest visitorKeys
twada Jan 15, 2019
13a16e1
test: skip modifying argument if SpreadElement appears immediately be…
twada Jan 15, 2019
22648f2
refactor: early return when callee is not empowered
twada Jan 17, 2019
76471ed
refactor: extract variable to remove duplication
twada Jan 17, 2019
f2f7bf0
fix: fix wrong index evaluation
twada Jan 17, 2019
b406925
refactor: reordering and comments for readability
twada Jan 18, 2019
f270de9
refactor: introduce pattern matcher object to normalize pattern confi…
twada Jan 18, 2019
3a5980e
test: rename
twada Jan 18, 2019
e044817
perf: store argument match index only
twada Jan 19, 2019
2239e8e
perf: parse once then clone results
twada Jan 19, 2019
2c603f4
chore: upgrade espurify and call-matcher
twada Jan 21, 2019
2d0755e
perf: introduce tiny call-matcher alternative based on call-signature
twada Jan 22, 2019
3ebe93e
perf: fallback to call-matcher if call-signature parsing is failed
twada Jan 22, 2019
7fcee02
style: nits
twada Jan 22, 2019
a59d303
Merge branch 'call-sig' into power-assert-2
twada Jan 22, 2019
5f80b1d
refactor: modernize embedded recorder classes
twada Jan 23, 2019
c42ebcf
refactor: simplify fallback output to reduce array reference in gener…
twada Jan 25, 2019
0c671a0
refactor: modernize to-be-skipped.js with new unit tests
twada Jan 29, 2019
eaf2e8e
test: maint unit test for new simplified recorder
twada Jan 29, 2019
951da77
test: maint fixture tests for new simplified recorder
twada Jan 29, 2019
b89371f
refactor: modernize codebase
twada Jan 29, 2019
9aec922
refactor: modernize codebase (var -> const, let)
twada Jan 29, 2019
7bca2a5
refactor: modernize codebase (class syntax)
twada Jan 29, 2019
78c428d
refactor: extract parseWithWrapper method out
twada Jan 29, 2019
b5a5818
refactor: eliminate core-js dependency
twada Jan 29, 2019
b498799
test: testing AssertionMetadata and AssertionMessage generation
twada Jan 29, 2019
66e7b8e
Merge branch 'modernize' into power-assert-2
twada Jan 29, 2019
11ddee5
style: apply semistandard
twada Jan 29, 2019
1d9d7cf
docs(README): semistandard badge
twada Jan 29, 2019
bb5da1c
Merge branch 'semistandard' into power-assert-2
twada Jan 29, 2019
a76da9d
chore: update babel to 7.3.0 and sync visitorKeys
twada Jan 29, 2019
87a7c88
perf: use Array#includes for type.name
twada Jan 31, 2019
4a2481c
chore: dealing with undefined
twada Jan 31, 2019
971a790
Merge branch 'string-types' into power-assert-2
twada Feb 3, 2019
3dfbe6e
refactor: enclose to-be-captured.js and to-be-skipped.js in closure
twada Jan 31, 2019
62b55c9
refactor: make short functions one liner
twada Feb 1, 2019
70d3925
Merge branch 'closures' into power-assert-2
twada Feb 3, 2019
96e9efa
feat: configure `embedAst` value automatically
twada Feb 3, 2019
95843db
Merge branch 'configure-embedast-automatically' into power-assert-2
twada Feb 3, 2019
cd249de
chore: run lint for each test run
twada Feb 3, 2019
265f2da
style: avoid yoda
twada Feb 3, 2019
bccc65e
refactor: more information hiding, less number of properties
twada Feb 4, 2019
ec67ec0
refactor: just a reordering
twada Feb 5, 2019
b02551b
refactor: start extracting ArgumentModification class
twada Feb 7, 2019
5f236f3
refactor: make tests pass
twada Feb 7, 2019
22934e4
refactor: extract argument-modification.js out and make all tests pass
twada Feb 7, 2019
5fee357
refactor: remove unused methods and variables
twada Feb 7, 2019
a2c376e
refactor: reconcile differences
twada Feb 7, 2019
405b051
refactor: eliminate `this` references to reconcile differences
twada Feb 7, 2019
00a2548
refactor: eliminate `this` references to reconcile differences
twada Feb 7, 2019
0222319
refactor: extract create-node.js out to eliminate duplication
twada Feb 7, 2019
ae1e620
refactor: move createNewArgumentRecorderNode to create-node.js
twada Feb 8, 2019
8fa256d
refactor: rename purifyAst to cloneAst
twada Feb 8, 2019
6f2f0dc
refactor: eliminate duplication
twada Feb 8, 2019
5d59558
Merge branch 'argument-modification' into power-assert-2
twada Feb 8, 2019
bc58566
refactor: function as configuration to gain shorter generated code
twada Feb 8, 2019
3b3adee
refactor: use positional parameters and shorthand notation for shorte…
twada Feb 9, 2019
534ebec
refactor: invoke LocationDetector only once
twada Feb 9, 2019
b135e49
refactor: prefer side effect free function
twada Feb 9, 2019
065420d
chore: count propsNode.properties
twada Feb 12, 2019
76314a0
refactor: generate filepath for each assertion since input code would…
twada Feb 12, 2019
cd5a77d
Merge branch 'metadata-function' into power-assert-2
twada Feb 12, 2019
caa29cf
test: update babel and sync with latest visitorKeys
twada Feb 12, 2019
78c37bb
refactor: use SpreadElement to gain readability
twada Feb 13, 2019
a2140b2
refactor: inline shorthand object expressions to gain readability
twada Feb 14, 2019
0153a35
fix: use Object.assign since Node6 does not support Spread Properties
twada Feb 14, 2019
d6a4768
fix: use Object.assign since Node6 does not support Spread Properties
twada Feb 14, 2019
618076b
chore(package): upgrade espower-location-detector
twada Feb 16, 2019
7c614ef
docs: update JSDoc
twada Feb 16, 2019
1d25fac
chore: update package-lock.json
twada Feb 16, 2019
fd13cb8
refactor: prefer method call
twada May 10, 2019
bb653c7
refactor: use `parameter` for declarations, `argument` for actual values
twada May 10, 2019
eb61e82
chore: add TODO
twada May 21, 2019
9155a3b
chore: per argument renderer configuration
twada May 21, 2019
d36a98c
chore: update package-lock.json
twada May 21, 2019
59cb2fb
test: sync with latest visitorKeys
twada May 21, 2019
a86ed54
fix: dealing with node not to be captured that appears immediately be…
twada Jun 3, 2019
257bdfc
test: update expected output
twada Jun 3, 2019
92d9ef5
docs: update jsdoc
twada Jun 3, 2019
bd3153a
Merge branch 'case-skip-capturing-toplevel-expression' into power-ass…
twada Jun 4, 2019
c13a70b
feat: embed instrumentation patterns as JSON
twada Jul 3, 2019
ea55fba
test: update expected output
twada Jul 3, 2019
1e8dd3e
feat: embed transpiler name and its version into output
twada Jul 8, 2019
7e635e3
test: update expected output
twada Jul 8, 2019
77c4edd
Merge branch 'embed-transpiler-version' into power-assert-2
twada Jul 8, 2019
658a969
chore(travis): add Node12, drop Node6 and Node11
twada Jul 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: node_js
sudo: false
node_js:
- "6" # to be removed on "April 2019"
- "8" # to be removed on "December 2019"
- "10" # to be removed on "April 2021"
- "11" # to be removed on "June 2019"
- "12" # to be removed on "April 2022"
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Babel plugin for power-assert.

[![Build Status][travis-image]][travis-url]
[![NPM version][npm-image]][npm-url]
[![Dependency Status][depstat-image]][depstat-url]
[![Code Style][style-image]][style-url]
[![License][license-image]][license-url]


Expand Down Expand Up @@ -318,7 +318,6 @@ You can customize configs such as assertion patterns via [.babelrc](https://babe
],
"plugins": [
["babel-plugin-espower", {
"embedAst": true,
"patterns": [
"assert.isNull(object, [message])",
"assert.same(actual, expected, [message])",
Expand All @@ -336,7 +335,6 @@ require('@babel/register')({
presets: [...],
plugins: [
['babel-plugin-espower', {
embedAst: true,
patterns: [
'assert.isNull(object, [message])',
'assert.same(actual, expected, [message])',
Expand All @@ -356,7 +354,6 @@ var transformed = babel.transform(jsCode, {
presets: [...],
plugins: [
['babel-plugin-espower', {
embedAst: true,
patterns: [
'assert.isNull(object, [message])',
'assert.same(actual, expected, [message])',
Expand Down Expand Up @@ -390,7 +387,7 @@ Configuration options for `babel-plugin-espower`. If not passed, default options
'assert.deepStrictEqual(actual, expected, [message])',
'assert.notDeepStrictEqual(actual, expected, [message])'
],
embedAst: true,
embedAst: false, // false by default, true if there are some syntax plugins configured
visitorKeys: babel.types.VISITOR_KEYS,
astWhiteList: babel.types.BUILDER_KEYS,
sourceRoot: process.cwd(),
Expand All @@ -402,6 +399,7 @@ Configuration options for `babel-plugin-espower`. If not passed, default options
#### options.embedAst

If you want to use non-ECMASCript-standard features such as JSX tags in your `assert()`, you should set `embedAst` option to `true`.
Since 4.0.0, `embedAst` option is configured automatically.

```js
assert(shallow(<Foo />).is('.foo'));
Expand Down Expand Up @@ -444,8 +442,8 @@ Licensed under the [MIT](https://github.com/power-assert-js/babel-plugin-espower
[travis-url]: https://travis-ci.org/power-assert-js/babel-plugin-espower
[travis-image]: https://secure.travis-ci.org/power-assert-js/babel-plugin-espower.svg?branch=master

[depstat-url]: https://gemnasium.com/power-assert-js/babel-plugin-espower
[depstat-image]: https://gemnasium.com/power-assert-js/babel-plugin-espower.svg
[style-url]: https://github.com/Flet/semistandard
[style-image]: https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg

[license-url]: https://github.com/power-assert-js/babel-plugin-espower/blob/master/LICENSE
[license-image]: https://img.shields.io/badge/license-MIT-brightgreen.svg
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* babel-plugin-espower:
* Babel plugin for power-assert
*
*
* https://github.com/power-assert-js/babel-plugin-espower
*
* Copyright (c) 2015-2019 Takuto Wada
Expand Down
132 changes: 132 additions & 0 deletions lib/argument-modification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
'use strict';

const define = require('./define-properties');
const createIsNodeToBeCaptured = require('./to-be-captured');
const { createNewArgumentRecorderNode, createNewAssertionMessageNode } = require('./create-node');

class ArgumentModification {
constructor (babel, match, options, cloneAst, assertionNodePath, metadataIdent) {
this.babel = babel;
this.options = options;
this.currentArgumentMatchResult = match;
this.assertionNodePath = assertionNodePath;
this.metadataIdent = metadataIdent;
this.cloneAst = cloneAst;
this.currentArgumentNodePath = null;
this.argumentModified = false;
this.messageUpdated = false;
this.isNodeToBeCaptured = createIsNodeToBeCaptured(babel.types);
}

toBeCaptured (nodePath) {
return this.isNodeToBeCaptured(nodePath);
}

isLeaving (nodePath) {
return this.currentArgumentNodePath === nodePath;
}

isCapturing () {
return true;
}

isMessageUpdated () {
return !!this.messageUpdated;
}

isArgumentModified () {
return !!this.argumentModified;
}

enter (nodePath) {
// entering target argument
this.currentArgumentNodePath = nodePath;
// create recorder per argument
this.argumentRecorder = createNewArgumentRecorderNode({
nodePath,
types: this.babel.types,
visitorKeys: this.options.visitorKeys,
metadataIdent: this.metadataIdent,
cloneAst: this.cloneAst,
calleeNode: this.assertionNodePath.node.callee,
matchIndex: this.currentArgumentMatchResult.index
});
}

leave (nodePath) {
const currentNode = nodePath.node;
const shouldCaptureValue = this.toBeCaptured(nodePath);
const pathToBeCaptured = shouldCaptureValue ? currentNode._espowerEspath : null;
const shouldCaptureArgument = this.isArgumentModified() || shouldCaptureValue;
const resultNode = shouldCaptureArgument ? this.captureArgument(currentNode, pathToBeCaptured) : currentNode;
if (this.currentArgumentMatchResult.name === 'message' && this.currentArgumentMatchResult.kind === 'optional') {
this.messageUpdated = true;
// enclose it in AssertionMessage
return createNewAssertionMessageNode({
nodePath,
types: this.babel.types,
visitorKeys: this.options.visitorKeys,
metadataIdent: this.metadataIdent,
cloneAst: this.cloneAst,
originalMessageNode: resultNode,
matchIndex: this.currentArgumentMatchResult.index
});
} else {
return resultNode;
}
}

captureNode (nodePath) {
const currentNode = nodePath.node;
const espath = currentNode._espowerEspath;
return this.insertRecorder(currentNode, espath, '_tap');
}

captureArgument (currentNode, espath) {
return this.insertRecorder(currentNode, espath, '_rec');
}

insertRecorder (currentNode, espath, methodName) {
const receiver = this.argumentRecorder;
const types = this.babel.types;
const args = [
currentNode
];
if (espath) {
args.push(types.valueToNode(espath));
}
const newNode = types.callExpression(
types.memberExpression(receiver, types.identifier(methodName)),
args
);
define(newNode, { _generatedByEspower: true });
this.argumentModified = true;
return newNode;
}
}

class NoModification {
enter (nodePath) {
this.currentArgumentNodePath = nodePath;
}
leave (nodePath) {
return nodePath;
}
isLeaving (nodePath) {
return this.currentArgumentNodePath === nodePath;
}
isCapturing () {
return false;
}
isMessageUpdated () {
return false;
}
isArgumentModified () {
return false;
}
}

module.exports = {
ArgumentModification,
NoModification
};
165 changes: 165 additions & 0 deletions lib/argument-recorder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
'use strict';
module.exports = function () {
const isPromiseLike = (o) => o !== null &&
typeof o === 'object' &&
typeof o.then === 'function' &&
typeof o.catch === 'function';

const mark = (_this, s) => {
return function () {
const args = Array.from(arguments);
_this.status = s;
_this.value = (args.length === 1) ? args[0] : args;
};
};

class $Promise$ {
constructor (prms) {
this.status = 'pending';
prms.then(mark(this, 'resolved'), mark(this, 'rejected'));
}
}

const wrap = (v) => isPromiseLike(v) ? new $Promise$(v) : v;

class ArgumentRecorder {
/**
* @typedef {Object} AssertionMetadata
* @property {string} content
* @property {string} filepath
* @property {number} line
* @property {number} version
* @property {string} pattern
* @property {array} params
* @property {boolean} [async] - true if enclosed in async function
* @property {boolean} [generator] - true if enclosed in generator function
* @property {string} [ast] - stringified AST
* @property {string} [tokens] - stringified tokens
* @property {string} [visitorKeys] - stringified visitorKeys
*/

/**
* record argument value and metadata silently
* @param {function} callee - callee of target argument
* @param {AssertionMetadata} am - generated metadata for target assertion
* @param {number} matchIndex - index of matched parameter
*/
constructor (callee, am, matchIndex) {
this._callee = callee;
this._am = am;
this._logs = [];
this._recorded = null;
this._val = null;
this._idx = matchIndex;
const conf = am.params[matchIndex];
this._isBlock = !!conf.block;
}

/**
* @return {AssertionMetadata} - AssertionMetadata for target assertion
*/
metadata () {
return this._am;
}

/**
* @return {number} - index of matched parameter
*/
matchIndex () {
return this._idx;
}

/**
* @return {*} - recorded actual value of target argument
*/
val () {
return this._val;
}

/**
* tap capturable node value with its espath then store them as a Log
* @param {*} value - actual value of target node
* @param {string} espath - espath of target node in AST
* @return {*} - the original value
*/
_tap (value, espath) {
this._logs.push({
value: wrap(value),
espath
});
return value;
}

/**
* record argument value silently then clear captured logs
* optionally, proxy block argument then store its result as a Log
* @param {*} value - actual value of target argument
* @param {string} [espath] - espath of target node in AST
* @return {*|ArgumentRecorder} - ArgumentRecorder or actual value of target argument
*/
_rec (value, espath) {
const empowered = this._callee && this._callee._empowered;
try {
if (!empowered) return value;
if (!espath) return this;

const log = {
value: wrap(value),
espath
};
this._logs.push(log);

if (this._isBlock && empowered && typeof value === 'function') {
value = new Proxy(value, {
apply (target, thisArg, args) {
try {
const ret = target.apply(thisArg, args);
log.value = wrap(ret);
return ret;
} catch (e) {
log.value = e;
throw e;
}
}
});
}

return this;
} finally {
if (empowered) {
this._recorded = {
value,
logs: [].concat(this._logs)
};
}
this._val = value; // actual value of target argument
this._logs = []; // clear logs
}
}

/**
* @typedef {Object} Log
* @property {*} value - recorded actual value of target node
* @property {string} espath - espath of target node in AST
*/

/**
* @typedef {Object} RecordedData
* @property {*} value - recorded actual value of target argument
* @property {Log[]} logs - recorded Logs
*/

/**
* return RecordedData then clear cache
* @return {RecordedData} - captured value and metadata of target argument
*/
eject () {
const ret = this._recorded;
this._recorded = null;
this._val = null;
return ret;
}
}

return ArgumentRecorder;
};
Loading