Skip to content

Commit

Permalink
🏗 Refactor transform-log-asserts (ampproject#24028)
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell authored and rsimha committed Aug 19, 2019
1 parent 69529a7 commit 08bc72f
Show file tree
Hide file tree
Showing 41 changed files with 394 additions and 116 deletions.
196 changes: 111 additions & 85 deletions build-system/babel-plugins/babel-plugin-transform-amp-asserts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,111 +14,137 @@
* limitations under the License.
*/

function isRemovableMethod(t, node, names) {
if (!node || !t.isIdentifier(node)) {
return false;
}
return names.some(x => {
return t.isIdentifier(node, {name: x});
});
}

const typeMap = {
'assertElement': '!Element',
'assertElement': 'Element',
'assertString': 'string',
'assertNumber': 'number',
'assertBoolean': 'boolean',
'assertArray': '!Array',
'assertArray': 'Array',
};

const removableDevAsserts = [
'assert',
'fine',
'assertElement',
'assertString',
'assertNumber',
'assertBoolean',
'assertArray',
];

const removableUserAsserts = ['fine'];
const REMOVABLE = {
dev: [
'assert',
'fine',
'assertElement',
'assertString',
'assertNumber',
'assertBoolean',
'assertArray',
],
user: ['fine'],
};

module.exports = function(babel) {
const {types: t} = babel;
const {types: t, template} = babel;

/**
* @param {!NodePath} path
* @param {!Array<string>} names
* @return {boolean}
*/
function isRemovableMethod(path, names) {
return names.some(name => {
return path.isIdentifier({name});
});
}

/**
* @param {!NodePath} path
* @param {boolean} assertion
* @param {string|undefined} type
*/
function eliminate(path, assertion, type) {
const argument = path.get('arguments.0');
if (!argument) {
if (assertion) {
throw path.buildCodeFrameError('assertion without a parameter!');
}

// This is to resolve right hand side usage of expression where
// no argument is passed in. This bare undefined value is eventually
// stripped by Closure Compiler.
path.replaceWith(t.identifier('undefined'));
return;
}

const arg = argument.node;
if (assertion) {
const evaluation = argument.evaluate();

// If we can statically evaluate the value to a falsey expression
if (evaluation.confident) {
if (type) {
if (typeof evaluation.value !== type) {
path.replaceWith(template.ast`
(function() {
throw new Error('static type assertion failure');
}());
`);
return;
}
} else if (!evaluation.value) {
path.replaceWith(template.ast`
(function() {
throw new Error('static assertion failure');
}());
`);
return;
}
}
}

path.replaceWith(t.parenthesizedExpression(arg));

if (type) {
// If it starts with a capital, make the type non-nullable.
if (/^[A-Z]/.test(type)) {
type = '!' + type;
}
// Add a cast annotation to fix type.
path.addComment('leading', `* @type {${type}} `);
}

const {parenthesized} = path.node.extra || {};
if (parenthesized) {
path.replaceWith(t.parenthesizedExpression(path.node));
}
}

return {
visitor: {
CallExpression(path) {
const {node} = path;
const {callee} = node;
const {parenthesized} = node.extra || {};
const callee = path.get('callee');

if (callee.isIdentifier({name: 'devAssert'})) {
return eliminate(path, true, '');
}

const isDirectCallExpression = t.isIdentifier(callee, {
name: 'devAssert',
});
const isMemberAndCallExpression =
t.isMemberExpression(callee) && t.isCallExpression(callee.object);
callee.isMemberExpression() &&
callee.get('object').isCallExpression();

if (!(isDirectCallExpression || isMemberAndCallExpression)) {
if (!isMemberAndCallExpression) {
return;
}

// `property` here is the identifier we want to evaluate such as the
// `devAssert` in a direct call or the `assert` in a `dev().assert()`
// call.
let property = null;
let args = null;
// `type` is left null in a direct call expression like `devAssert`
let type = null;

if (isDirectCallExpression) {
property = node.callee;
args = node.arguments[0];
} else if (isMemberAndCallExpression) {
property = callee.property;

const logCallee = callee.object.callee;
const isRemovableDevCall =
t.isIdentifier(logCallee, {name: 'dev'}) &&
isRemovableMethod(t, property, removableDevAsserts);

const isRemovableUserCall =
t.isIdentifier(logCallee, {name: 'user'}) &&
isRemovableMethod(t, property, removableUserAsserts);

if (!(isRemovableDevCall || isRemovableUserCall)) {
return;
}
args = path.node.arguments[0];
type = typeMap[property.name];
const logCallee = callee.get('object.callee');
let removable = [];

if (
logCallee.isIdentifier({name: 'dev'}) ||
logCallee.isIdentifier({name: 'user'})
) {
removable = REMOVABLE[logCallee.node.name];
}

if (args) {
if (parenthesized) {
path.replaceWith(t.parenthesizedExpression(args));
path.skip();
// If it is not an assert type, we won't need to do type annotation.
// If it has no type that we can cast to, then we also won't need to
// do type annotation.
} else if (!property.name.startsWith('assert') || !type) {
path.replaceWith(args);
} else {
// Special case null value argument since it's mostly used for
// interface methods with no implementation which will most likely
// get DCE'd by Closure Compiler since they are unused code methods.
if (args.type === 'NullLiteral') {
return;
} else {
path.replaceWith(t.parenthesizedExpression(args));
// Add a cast annotation to fix type.
path.addComment('leading', `* @type {${type}} `);
}
}
} else {
// This is to resolve right hand side usage of expression where
// no argument is passed in. This bare undefined value is eventually
// stripped by Closure Compiler.
path.replaceWith(t.identifier('undefined'));
const prop = callee.get('property');
if (!isRemovableMethod(prop, removable)) {
return;
}

const method = prop.node.name;
eliminate(path, method.startsWith('assert'), typeMap[method]);
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
*/
user().assert(1 + 1);
let result = user().assert(user(), 'hello', 'world');
let result2 = user().assert();
let result2 = user().assert(true);
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
*/
user().assert(1 + 1);
let result = user().assert(user(), 'hello', 'world');
let result2 = user().assert();
let result2 = user().assert(true);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/
userAssert(1 + 1);
userAssert(2 + 2);
userAssert((2 + 2));
userAssert();
let result = userAssert(dev(), 'hello', 'world');
let result2 = userAssert();
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
*/

/** @type {x} */

/** @type {!Element} */
(dev());

function hello() {
return (
/** @type {!Element} */

/** @type {x} */
(dev())
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dev().assertBoolean(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../../../babel-plugin-transform-amp-asserts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
throw new Error('static type assertion failure');
})();
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ const falsey = false;
dev().assertBoolean(falsey);
dev().assertBoolean(true);
let result = dev().assertBoolean(false, 'hello', 'world');
let result2 = dev().assertBoolean();
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,3 @@ const falsey = false;
let result =
/** @type {boolean} */
(false);
let result2 = undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@
*/
dev().assertElement(element);
let result = dev().assertElement(element, 'hello', 'world');
let result2 = dev().assertElement();
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@
let result =
/** @type {!Element} */
(element);
let result2 = undefined;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dev().assertNumber(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../../../babel-plugin-transform-amp-asserts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
throw new Error('static type assertion failure');
})();
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ let num = 5;
dev().assertNumber(num);
dev().assertNumber(1 + 1);
let result = dev().assertNumber(3, 'hello', 'world');
let result2 = dev().assertNumber();
dev().assertNumber(0);
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ let num = 5;
let result =
/** @type {number} */
(3);
let result2 = undefined;

/** @type {number} */
(0);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dev().assertString(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../../../babel-plugin-transform-amp-asserts"]
}
Loading

0 comments on commit 08bc72f

Please sign in to comment.