Skip to content

Commit

Permalink
Remove side effect exports (#4261)
Browse files Browse the repository at this point in the history
* Do not export a side effect

Side effects can’t be DCE’d.

* Fix side effect in Performance

* Remove platform side effect

* Remove Timer#now

* eliminate dev().assert() and dev().fine() calls from src/log.js module

* Fix timer side effect

* Fix Log side effect

* Fix tests - part 1

* These damn tests

* Fix service worker tests

* Fix Analytics tests

* Linting

* Fix error

* Fix Signin

* fixes

* Disable test

* Disable check-types

* Fix merge
  • Loading branch information
jridgewell authored Aug 3, 2016
1 parent 045242e commit fe63400
Show file tree
Hide file tree
Showing 161 changed files with 1,028 additions and 828 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"no-div-regex": 2,
"no-dupe-keys": 2,
"no-eval": 2,
"no-export-side-effect": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-for-of-statement": 2,
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ script:
- npm run ava
- gulp lint
- gulp build --fortesting --css-only
- gulp check-types
# TODO(jridgewell, #4347) Enable once assertions prove truthiness again.
# - gulp check-types
- gulp dist --fortesting
- gulp presubmit
# dep-check needs to occur after build since we rely on build to generate
Expand Down
10 changes: 5 additions & 5 deletions 3p/3p.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ let syncScriptLoads = 0;
* @param {ThirdPartyFunctionDef} draw Function that draws the 3p integration.
*/
export function register(id, draw) {
dev.assert(!registrations[id], 'Double registration %s', id);
dev().assert(!registrations[id], 'Double registration %s', id);
registrations[id] = draw;
}

Expand All @@ -57,7 +57,7 @@ export function register(id, draw) {
*/
export function run(id, win, data) {
const fn = registrations[id];
user.assert(fn, 'Unknown 3p: ' + id);
user().assert(fn, 'Unknown 3p: ' + id);
fn(win, data);
}

Expand Down Expand Up @@ -218,7 +218,7 @@ export function computeInMasterFrame(global, taskId, work, cb) {
export function validateDataExists(data, mandatoryFields) {
for (let i = 0; i < mandatoryFields.length; i++) {
const field = mandatoryFields[i];
user.assert(data[field],
user().assert(data[field],
'Missing attribute for %s: %s.', data.type, field);
}
}
Expand All @@ -239,7 +239,7 @@ export function validateExactlyOne(data, alternativeFields) {
}
}

user.assert(countFileds === 1,
user().assert(countFileds === 1,
'%s must contain exactly one of attributes: %s.',
data.type,
alternativeFields.join(', '));
Expand Down Expand Up @@ -268,7 +268,7 @@ export function validateData(data, allowedFields) {
field in defaultAvailableFields) {
continue;
}
user.assert(allowedFields.indexOf(field) != -1,
user().assert(allowedFields.indexOf(field) != -1,
'Unknown attribute for %s: %s.', data.type, field);
}
}
2 changes: 1 addition & 1 deletion 3p/facebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function getFacebookSdk(global, cb) {
*/
export function facebook(global, data) {
const embedAs = data.embedAs || 'post';
user.assert(['post', 'video'].indexOf(embedAs) !== -1,
user().assert(['post', 'video'].indexOf(embedAs) !== -1,
'Attribute data-embed-as for <amp-facebook> value is wrong, should be' +
' "post" or "video" was: %s', embedAs);
const fbPost = global.document.createElement('div');
Expand Down
12 changes: 6 additions & 6 deletions 3p/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,14 @@ const waitForRenderStart = [
*/
export function draw3p(win, data, configCallback) {
const type = data.type;
user.assert(win.context.location.originValidated != null,
user().assert(win.context.location.originValidated != null,
'Origin should have been validated');

user.assert(isTagNameAllowed(data.type, win.context.tagName),
user().assert(isTagNameAllowed(data.type, win.context.tagName),
'Embed type %s not allowed with tag %s', data.type, win.context.tagName);
if (configCallback) {
configCallback(data, data => {
user.assert(data,
user().assert(data,
'Expected configuration to be passed as first argument');
run(type, win, data);
});
Expand Down Expand Up @@ -413,7 +413,7 @@ function onResizeDenied(observerCallback) {
* @param {string} entityId See comment above for content.
*/
function reportRenderedEntityIdentifier(entityId) {
user.assert(typeof entityId == 'string',
user().assert(typeof entityId == 'string',
'entityId should be a string %s', entityId);
nonSensitiveDataPostMessage('entity-id', {
id: entityId,
Expand All @@ -438,7 +438,7 @@ export function validateParentOrigin(window, parentLocation) {
parentLocation.originValidated = false;
return;
}
user.assert(ancestors[0] == parentLocation.origin,
user().assert(ancestors[0] == parentLocation.origin,
'Parent origin mismatch: %s, %s',
ancestors[0], parentLocation.origin);
parentLocation.originValidated = true;
Expand Down Expand Up @@ -467,7 +467,7 @@ export function validateAllowedTypes(window, type, allowedTypes) {
if (defaultAllowedTypesInCustomFrame.indexOf(type) != -1) {
return;
}
user.assert(allowedTypes && allowedTypes.indexOf(type) != -1,
user().assert(allowedTypes && allowedTypes.indexOf(type) != -1,
'Non-whitelisted 3p type for custom iframe: ' + type);
}

Expand Down
2 changes: 1 addition & 1 deletion ads/google/a4a/traffic-experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function handleUrlParameters(win, experimentId, branches) {
forceExperimentBranch(win, experimentId, branches.experiment);
break;
default:
dev.warn('a4a-config', 'Unknown a4a URL parameter: ',
dev().warn('a4a-config', 'Unknown a4a URL parameter: ',
a4aParam[1]);
}
}
Expand Down
7 changes: 3 additions & 4 deletions ads/google/a4a/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {getAdCid} from '../../../src/ad-cid';
import {documentInfoFor} from '../../../src/document-info';
import {dev} from '../../../src/log';
import {getMode} from '../../../src/mode';
import {timer} from '../../../src/timer';
import {isProxyOrigin} from '../../../src/url';
import {viewerFor} from '../../../src/viewer';
import {viewportFor} from '../../../src/viewport';
Expand Down Expand Up @@ -171,7 +170,7 @@ function buildAdUrl(
{name: 'ref', value: referrer},
]
);
dtdParam.value = elapsedTimeWithCeiling(timer.now(), startTime);
dtdParam.value = elapsedTimeWithCeiling(Date.now(), startTime);
return buildUrl(
baseUrl, allQueryParams, MAX_URL_LENGTH, {name: 'trunc', value: '1'});
}
Expand Down Expand Up @@ -214,7 +213,7 @@ function iframeNestingDepth(global) {
win = win.parent;
depth++;
}
dev.assert(win == global.top);
dev().assert(win == global.top);
return depth;
}

Expand Down Expand Up @@ -299,7 +298,7 @@ function secondWindowFromTop(global) {
while (secondFromTop.parent != secondFromTop.parent.parent) {
secondFromTop = secondFromTop.parent;
}
dev.assert(secondFromTop.parent == global.top);
dev().assert(secondFromTop.parent == global.top);
return secondFromTop;
}

Expand Down
37 changes: 37 additions & 0 deletions build-system/eslint-rules/no-export-side-effect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2016 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.
*/

module.exports = function(context) {
return {
ExportNamedDeclaration: function(node) {
if (node.declaration) {
var declaration = node.declaration;
if (declaration.type === 'VariableDeclaration') {
declaration.declarations
.map(function(declarator) {
return declarator.init
}).filter(function(init) {
return init && /(?:Call|New)Expression/.test(init.type);
}).forEach(function(init) {
context.report(init, 'Cannot export side-effect');
});
}
} else if (node.specifiers) {
context.report(node, 'Side-effect linting not implemented');
}
},
};
};
Binary file modified build-system/runner/dist/runner.jar
Binary file not shown.
44 changes: 34 additions & 10 deletions build-system/runner/src/org/ampproject/AmpPass.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@ public AmpPass(AbstractCompiler compiler, boolean isProd, Set<String> stripTypeS
}

@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (isCallRemovable(n)) {
Node call = n.getGrandparent();
maybeEliminateCallExceptFirstParam(call, call.getParent());
// Remove `dev.assert` calls and preserve first argument if any.
if (isNameStripType(n, ImmutableSet.of( "dev.assert"))) {
} else if (isNameStripType(n, ImmutableSet.of( "dev.assert"))) {
maybeEliminateCallExceptFirstParam(n, parent);
// Remove any `stripTypes` passed in outright like `dev.warn`.
// Remove any `stripTypes` passed in outright like `dev.fine`.
} else if (isNameStripType(n, stripTypeSuffixes)) {
removeExpression(n, parent);
// Remove any `getMode().localDev` and `getMode().test` calls and replace it with `false`.
Expand All @@ -82,6 +85,28 @@ public AmpPass(AbstractCompiler compiler, boolean isProd, Set<String> stripTypeS
maybeReplaceRValueInVar(n, assignmentReplacements);
}
}

private boolean isCallRemovable(Node n) {
// Looks for a label named "assert" or "fine" then traverses up
// looking for a "Call". This more or less looks for
// module$src$log.dev().assert(); or module$src$log.dev().fine();
if (n != null && n.isString() &&
// Refactor to read this in from passed in from command line runner instead
(n.getString() == "assert" || n.getString() == "fine")) {
Node call = n.getGrandparent();
if (call == null || !call.isCall()) {
return false;
}
// Look for a module named src/log.js and a `dev` export that is called.
Node siblingcall = n.getParent().getFirstChild();
if (siblingcall.isCall()) {
Node getprop = siblingcall.getFirstChild();
return getprop != null && getprop.isGetProp() &&
getprop.matchesQualifiedName("module$src$log.dev");
}
}
return false;
}

private void maybeReplaceRValueInVar(Node n, Map<String, Node> map) {
if (n != null && (n.isVar() || n.isLet() || n.isConst())) {
Expand Down Expand Up @@ -154,7 +179,7 @@ private boolean isNameStripType(Node n, Set<String> suffixes) {
return false;
}
Node getprop = n.getFirstChild();
if (getprop == null) {
if (getprop == null || !getprop.isGetProp()) {
return false;
}
return qualifiedNameEndsWithStripType(getprop, suffixes);
Expand All @@ -170,21 +195,20 @@ private void removeExpression(Node n, Node parent) {
compiler.reportCodeChange();
}

private void maybeEliminateCallExceptFirstParam(Node n, Node p) {
Node call = n.getFirstChild();
if (call == null) {
private void maybeEliminateCallExceptFirstParam(Node call, Node p) {
Node getprop = call.getFirstChild();
if (getprop == null) {
return;
}

Node firstArg = call.getNext();
Node firstArg = getprop.getNext();
if (firstArg == null) {
p.removeChild(n);
compiler.reportCodeChange();
removeExpression(call, p);
return;
}

firstArg.detachFromParent();
p.replaceChild(n, firstArg);
p.replaceChild(call, firstArg);
compiler.reportCodeChange();
}

Expand Down
Loading

0 comments on commit fe63400

Please sign in to comment.