Skip to content

Commit

Permalink
🏗🚀 Speed up amp get-zindex ~4x (#36836)
Browse files Browse the repository at this point in the history
These changes speed up the `amp get-zindex` task by ~4x.

1. Use `babel` for simple AST parsing instead of `jscodeshift`. 
1. Shortcut by checking pattern inline instead of grepping early. 

Additionally, we uninstall `jscodeshift` as a global dependency. The only task remaining that uses it is `sweep-experiments`, which does not run on CI. It uses `npx` to temporarily install on execution instead.
  • Loading branch information
alanorozco committed Nov 9, 2021
1 parent 6cb2cb4 commit a8baaea
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 987 deletions.
78 changes: 17 additions & 61 deletions build-system/tasks/get-zindex/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ const fs = require('fs');
const path = require('path');
const Postcss = require('postcss');
const prettier = require('prettier');
const {
getJscodeshiftReport,
jscodeshiftAsync,
} = require('../../test-configs/jscodeshift');
const {getStdout} = require('../../common/process');
const {getZindexJs} = require('./js');
const {gray, magenta} = require('kleur/colors');
const {logLocalDev, logOnSameLineLocalDev} = require('../../common/logging');
const {writeDiffOrFail} = require('../../common/diff');
Expand Down Expand Up @@ -120,65 +116,25 @@ async function getZindexSelectors(glob, cwd = '.') {
* @param {string=} cwd
* @return {!Promise<Object>}
*/
function getZindexChainsInJs(glob, cwd = '.') {
return new Promise((resolve) => {
const files = fastGlob
.sync(glob, {cwd})
.map((file) => path.join(cwd, file));

const filesIncludingString = getStdout(
['grep -irl "z-*index"', ...files].join(' ')
)
.trim()
.split('\n');

const result = {};

let resultCountInverse = filesIncludingString.length;

if (resultCountInverse === 0) {
// We don't expect this fileset to be empty since it's unlikely that we
// never change the z-index from JS, but we add this just in case to
// prevent hanging infinitely.
resolve(result);
return;
}

const process = jscodeshiftAsync([
'--dry',
'--no-babel',
`--transform=${__dirname}/jscodeshift/collect-zindex.js`,
...filesIncludingString,
]);
const {stderr, stdout} = process;

stderr.on('data', (data) => {
throw new Error(data.toString());
});

stdout.on('data', (data) => {
const reportLine = getJscodeshiftReport(data.toString());

if (!reportLine) {
return;
}

const [filename, report] = reportLine;
const relative = path.relative(cwd, filename);
async function getZindexChainsInJs(glob, cwd = '.') {
const files = (await fastGlob(glob, {cwd})).map((file) =>
path.join(cwd, file)
);

const resultEntries = await Promise.all(
files.map(async (filename) => {
logChecking(filename);
const relative = path.relative(cwd, filename);
const found = await getZindexJs(filename);
return [relative, found];
})
);

try {
const reportParsed = JSON.parse(report);
if (reportParsed.length) {
result[relative] = reportParsed.sort(sortedByEntryKey);
}
if (--resultCountInverse === 0) {
resolve(result);
}
} catch (_) {}
});
});
return Object.fromEntries(
resultEntries
// eslint-disable-next-line local/no-deep-destructuring
.filter(([, {length}]) => length)
);
}

/**
Expand Down
146 changes: 146 additions & 0 deletions build-system/tasks/get-zindex/js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
const types = require('@babel/types');
const {parse} = require('@babel/parser');
const {readFile} = require('fs-extra');

const zIndexRegExp = /^z-?index$/i;

/**
* @param {types.CallExpression} node
* @return {string|number|undefined}
*/
function getCallExpressionZIndexValue(node) {
for (let i = 1; i < node.arguments.length; i++) {
const argument = node.arguments[i];
const previous = node.arguments[i - 1];
if (
(types.isStringLiteral(argument) || types.isNumericLiteral(argument)) &&
argument.value !== '' &&
types.isStringLiteral(previous) &&
zIndexRegExp.test(previous.value)
) {
return argument.value;
}
}
}

/**
* @param {string} source
* @param {types.Node} node
* @return {string}
*/
function getNodeSource(source, node) {
const {end, start} = node;
if (end == null || start == null) {
return '';
}
return source.substr(start, end - start);
}

/**
* @param {string} source
* @param {types.TraversalAncestors} ancestors
* @return {string}
*/
function getPropertyChain(source, ancestors) {
let propertyChain = '';

const path = ancestors.map(({node}) => node);
let at = path.pop();

while (at && types.isObjectProperty(at)) {
const part = getNodeSource(source, at.key);
if (types.isIdentifier(at.key)) {
propertyChain = `.${part}` + propertyChain;
} else {
propertyChain = `[${part}]` + propertyChain;
}
at = path.pop();
}

while (
at &&
!types.isCallExpression(at) &&
!types.isVariableDeclarator(at) &&
!types.isAssignmentExpression(at) &&
!types.isJSXAttribute(at) &&
!types.isProgram(at)
) {
at = path.pop();
}

if (types.isCallExpression(at)) {
return getNodeSource(source, at.callee);
}

if (types.isJSXAttribute(at)) {
const parent = path.pop();
if (types.isJSXOpeningElement(parent)) {
const name = getNodeSource(source, parent.name);
return `<${name} />`;
}
}

if (types.isAssignmentExpression(at)) {
return getNodeSource(source, at.left) + propertyChain;
}

if (types.isVariableDeclarator(at)) {
return getNodeSource(source, at.id) + propertyChain;
}

return '(unknown)';
}

/**
* @param {string} filename
* @return {Promise<[string, string][]>}
*/
async function getZindexJs(filename) {
const report = [];
const source = await readFile(filename, 'utf8');

// Prevent parsing if there's no chance of encountering string
if (!/z-*index/i.test(source)) {
return report;
}

const tree = parse(source, {
sourceType: 'module',
plugins: ['jsx'],
});

types.traverse(tree, (node, ancestors) => {
if (types.isObjectProperty(node)) {
const {value} = node;
if (!types.isStringLiteral(value) && !types.isNumericLiteral(value)) {
return;
}

const {key} = node;
const name = types.isStringLiteral(key)
? key.value
: types.isIdentifier(key)
? key.name
: null;
if (!name) {
return;
}

if (zIndexRegExp.test(name) && value.value !== '') {
ancestors.pop();
if (ancestors.length) {
report.push([getPropertyChain(source, ancestors), value.value]);
}
}
} else if (types.isCallExpression(node)) {
const value = getCallExpressionZIndexValue(node);
if (value != null) {
report.push([getNodeSource(source, node.callee), value]);
}
}
});

return report.sort(([a], [b]) => a.localeCompare(b));
}

module.exports = {getZindexJs};
115 changes: 0 additions & 115 deletions build-system/tasks/get-zindex/jscodeshift/collect-zindex.js

This file was deleted.

0 comments on commit a8baaea

Please sign in to comment.