@contrast/rewriter
Advanced tools
Comparing version 1.3.1 to 1.4.0
266
lib/index.js
@@ -18,148 +18,26 @@ /* | ||
const { transformSync } = require('@swc/core'); | ||
const Module = require('module'); | ||
const { default: traverse } = require('@babel/traverse'); | ||
const parser = require('@babel/parser'); | ||
const { default: generate } = require('@babel/generator'); | ||
const t = require('@babel/types'); | ||
const { expression, statement } = require('@babel/template'); | ||
const rewriterPath = require.resolve('@contrast/agent-swc-plugin'); | ||
const unwriterPath = require.resolve('@contrast/agent-swc-plugin-unwrite'); | ||
/** | ||
* factory | ||
*/ | ||
module.exports = function(core) { | ||
const rewriter = new Rewriter(core); | ||
return core.rewriter = rewriter; | ||
}; | ||
const prefix = Module.wrapper[0]; | ||
const suffix = Module.wrapper[1].replace(/;$/, '.apply(this, arguments);'); | ||
/** @typedef {'assess' | 'protect'} Mode */ | ||
/** | ||
* Babel will add a trailing semicolon in some cases. This will remove it if it | ||
* wasn't there to begin with. | ||
* @param {string} rewritten rewritten content | ||
* @param {string} orig original content before rewriting | ||
* @returns {string} | ||
*/ | ||
function removeAddedSemicolons(orig, rewritten) { | ||
if ( | ||
rewritten.charCodeAt(rewritten.length - 1) == 59 && | ||
orig.charCodeAt(orig.length - 1) != 59 | ||
) { | ||
rewritten = rewritten.substr(0, rewritten.length - 1); | ||
} | ||
return rewritten; | ||
} | ||
const rewriter = { | ||
/** @type {Set<Mode>} */ | ||
modes: new Set(), | ||
class Rewriter { | ||
constructor(deps) { | ||
const self = this; | ||
this.logger = deps.logger; | ||
this.visitors = []; | ||
this.installedModes = []; | ||
this.tokens = []; | ||
this.injections = []; | ||
this.methodLookups = {}; | ||
this.rewriteTransforms = { | ||
enter(...args) { | ||
for (const v of self.visitors) { | ||
v(...args); | ||
} | ||
}, | ||
Program: function Program(path, state) { | ||
if (state.wrap) { | ||
let [prefix, suffix] = Module.wrapper; | ||
prefix = prefix.trim(); | ||
suffix = suffix.trim().replace(/;$/, '.apply(this, arguments);'); | ||
/** | ||
* Sets the rewriter to 'assess' or 'protect' mode, enabling different | ||
* transforms. | ||
* @param {Mode} mode | ||
*/ | ||
install(mode) { | ||
this.modes.add(mode); | ||
}, | ||
path.node.body = [ | ||
statement(`${prefix} %%body%% ${suffix}`)({ | ||
body: path.node.body | ||
}) | ||
]; | ||
} | ||
if (state.inject) { | ||
path.unshiftContainer('body', self.injections); | ||
} | ||
}, | ||
CallExpression(path) { | ||
if (path.node.callee.name === 'eval') { | ||
path.node.arguments = [ | ||
t.callExpression(expression('global.ContrastMethods.eval')(), path.node.arguments) | ||
]; | ||
} | ||
}, | ||
BinaryExpression: function BinaryExpression(path) { | ||
const method = self.methodLookups[path.node.operator]; | ||
if (method) { | ||
path.replaceWith( | ||
t.callExpression( | ||
expression('ContrastMethods.%%method%%')({ method }), [ | ||
path.node.left, | ||
path.node.right | ||
] | ||
) | ||
); | ||
} | ||
} | ||
}; | ||
this.unwriteTransforms = { | ||
CallExpression(path) { | ||
const obj = path.node.callee.object; | ||
if (obj && obj.property && obj.property.name === 'ContrastMethods') { | ||
path.replaceWith(path.node.arguments[0]); | ||
} | ||
} | ||
}; | ||
this.install = function(mode) { | ||
self.installedModes.push(mode); | ||
!self.methodLookups.eval && (self.methodLookups = { | ||
eval: 'eval' | ||
}); | ||
!(self.injections.length === 2) && self.injections.push( | ||
statement( | ||
'const %%id%% = global.%%id%% || (() => { throw new SyntaxError(%%errMessage%%); })();' | ||
)({ | ||
id: 'ContrastMethods', | ||
errMessage: t.stringLiteral( | ||
'ContrastMethods undefined during compilation' | ||
) | ||
}), | ||
statement( | ||
'var %%name%% = global.ContrastMethods.%%id%% || %%name%%;' | ||
)({ name: 'Function', id: 'Function' }), | ||
); | ||
if (self.installedModes.includes('protect')) { | ||
// Protect doesn't have anything specific | ||
// for rewriting that should not be rewritten | ||
// in Assess (at least for now) | ||
} | ||
if (self.installedModes.includes('assess')) { | ||
Object.assign(self.methodLookups, { | ||
'+': 'plus', | ||
'===': 'tripleEqual', | ||
'!==': 'notTripleEqual', | ||
'==': 'doubleEqual', | ||
'!=': 'notDoubleEqual' | ||
}); | ||
Object.assign(self.rewriteTransforms, { | ||
AssignmentExpression(path) { | ||
if (path.node.operator !== '+=') return; | ||
path.replaceWith( | ||
t.assignmentExpression( | ||
'=', | ||
path.node.left, | ||
t.callExpression(expression('global.ContrastMethods.plus')(), [path.node.left, path.node.right]) | ||
) | ||
); | ||
} | ||
}); | ||
} | ||
self.tokens = Object.keys(self.methodLookups); | ||
}; | ||
} | ||
/** | ||
@@ -169,71 +47,59 @@ * @param {string} content the source code | ||
* @param {string} opts.filename e.g. 'index.js' | ||
* @param {boolean} opts.inject whether to inject contrast methods | ||
* @param {string} opts.sourceType script or module | ||
* @param {boolean} opts.wrap whether to wrap code in module wrap IIFE | ||
* @returns {object} | ||
* @param {boolean} opts.isModule if true, file is parsed as an ES module instead of a CJS script | ||
* @param {boolean} opts.inject if true, injects ContrastMethods on the global object | ||
* @param {boolean} opts.wrap if true, wraps the content with a modified module wrapper IIFE | ||
* @returns {import("@swc/core").Output} | ||
*/ | ||
rewrite(content, opts = { | ||
inject: false, | ||
wrap: false, | ||
sourceType: 'script', | ||
}) { | ||
opts.filename = opts.filename || 'no filename'; | ||
opts.sourceType = opts.sourceType || 'script'; | ||
rewrite(content, opts = {}) { | ||
if (opts.wrap) { | ||
content = `${prefix}${content}${suffix}`; | ||
} | ||
const state = { | ||
orig: String(content), | ||
deps: [], | ||
return transformSync(content, { | ||
filename: opts.filename, | ||
...opts | ||
}; | ||
if (this.tokens.every((token) => state.orig.indexOf(token) === -1)) { | ||
return { code: state.orig }; | ||
} | ||
const ast = parser.parse(state.orig, { | ||
plugins: [ | ||
'classPrivateMethods', | ||
'classPrivateProperties', | ||
'classProperties' | ||
], | ||
ranges: true, | ||
sourceType: state.sourceType, | ||
sourceFilename: state.filename, | ||
tokens: true | ||
isModule: opts.isModule, | ||
jsc: { | ||
target: 'es2019', // should work for node >14 | ||
experimental: { | ||
plugins: [ | ||
[ | ||
rewriterPath, | ||
{ | ||
assess: this.modes.has('assess'), | ||
inject: this.modes.has('assess') && opts.inject, | ||
}, | ||
], | ||
], | ||
}, | ||
}, | ||
sourceMaps: true, | ||
}); | ||
}, | ||
traverse(ast, this.rewriteTransforms, null, state); | ||
// TODO: Look into how effective this is | ||
traverse.cache.clear(); | ||
const result = generate( | ||
ast, | ||
{ | ||
jsonCompatibleStrings: true, | ||
sourceMaps: true, | ||
sourceFileName: state.filename | ||
}, | ||
state.orig | ||
); | ||
result.code = removeAddedSemicolons(content, result.code); | ||
result.deps = state.deps; | ||
return result; | ||
} | ||
/** | ||
* @param {string} code | ||
* @param {object} opts | ||
* @param {string} content | ||
* @returns {string} | ||
*/ | ||
unwrite(code, opts) { | ||
const ast = parser.parse(code); | ||
traverse(ast, this.unwriteTransforms); | ||
const unwritten = generate(ast, { jsonCompatibleStrings: true }).code; | ||
return removeAddedSemicolons(code, unwritten); | ||
} | ||
} | ||
unwrite(content) { | ||
return transformSync(content, { | ||
jsc: { | ||
target: 'es2019', // should work for node >14 | ||
experimental: { | ||
plugins: [[unwriterPath, {}]], | ||
}, | ||
}, | ||
}).code; | ||
}, | ||
}; | ||
module.exports.Rewriter = Rewriter; | ||
/** @typedef {{}} Core */ | ||
/** @typedef {typeof rewriter} Rewriter */ | ||
/** | ||
* @param {Core} core | ||
* @returns {Rewriter} | ||
*/ | ||
module.exports = function init(core) { | ||
core.rewriter = rewriter; | ||
return rewriter; | ||
}; |
{ | ||
"name": "@contrast/rewriter", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "A transpilation tool mainly used for instrumentation", | ||
@@ -20,10 +20,8 @@ "license": "SEE LICENSE IN LICENSE", | ||
"dependencies": { | ||
"@babel/generator": "^7.17.9", | ||
"@babel/parser": "^7.17.9", | ||
"@babel/template": "^7.16.7", | ||
"@babel/traverse": "^7.17.9", | ||
"@babel/types": "^7.16.7", | ||
"@contrast/agent-swc-plugin": "^1.1.0", | ||
"@contrast/agent-swc-plugin-unwrite": "^1.1.0", | ||
"@contrast/synchronous-source-maps": "^1.1.3", | ||
"@swc/core": "1.3.39", | ||
"multi-stage-sourcemap": "^0.3.1" | ||
} | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
5
6787
131
1
+ Added@swc/core@1.3.39
+ Added@contrast/agent-swc-plugin@1.5.1(transitive)
+ Added@contrast/agent-swc-plugin-unwrite@1.5.1(transitive)
+ Added@swc/core@1.3.391.5.29(transitive)
+ Added@swc/core-darwin-arm64@1.3.391.5.29(transitive)
+ Added@swc/core-darwin-x64@1.3.391.5.29(transitive)
+ Added@swc/core-linux-arm-gnueabihf@1.3.391.5.29(transitive)
+ Added@swc/core-linux-arm64-gnu@1.3.391.5.29(transitive)
+ Added@swc/core-linux-arm64-musl@1.3.391.5.29(transitive)
+ Added@swc/core-linux-x64-gnu@1.3.391.5.29(transitive)
+ Added@swc/core-linux-x64-musl@1.3.391.5.29(transitive)
+ Added@swc/core-win32-arm64-msvc@1.3.391.5.29(transitive)
+ Added@swc/core-win32-ia32-msvc@1.3.391.5.29(transitive)
+ Added@swc/core-win32-x64-msvc@1.3.391.5.29(transitive)
+ Added@swc/counter@0.1.3(transitive)
+ Added@swc/types@0.1.13(transitive)
- Removed@babel/generator@^7.17.9
- Removed@babel/parser@^7.17.9
- Removed@babel/template@^7.16.7
- Removed@babel/traverse@^7.17.9
- Removed@babel/types@^7.16.7
- Removed@babel/code-frame@7.25.7(transitive)
- Removed@babel/generator@7.25.7(transitive)
- Removed@babel/helper-string-parser@7.25.7(transitive)
- Removed@babel/helper-validator-identifier@7.25.7(transitive)
- Removed@babel/highlight@7.25.7(transitive)
- Removed@babel/parser@7.25.8(transitive)
- Removed@babel/template@7.25.7(transitive)
- Removed@babel/traverse@7.25.7(transitive)
- Removed@babel/types@7.25.8(transitive)
- Removed@jridgewell/gen-mapping@0.3.5(transitive)
- Removed@jridgewell/resolve-uri@3.1.2(transitive)
- Removed@jridgewell/set-array@1.2.1(transitive)
- Removed@jridgewell/sourcemap-codec@1.5.0(transitive)
- Removed@jridgewell/trace-mapping@0.3.25(transitive)
- Removedansi-styles@3.2.1(transitive)
- Removedchalk@2.4.2(transitive)
- Removedcolor-convert@1.9.3(transitive)
- Removedcolor-name@1.1.3(transitive)
- Removeddebug@4.3.7(transitive)
- Removedescape-string-regexp@1.0.5(transitive)
- Removedglobals@11.12.0(transitive)
- Removedhas-flag@3.0.0(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedjsesc@3.0.2(transitive)
- Removedms@2.1.3(transitive)
- Removedpicocolors@1.1.1(transitive)
- Removedsupports-color@5.5.0(transitive)
- Removedto-fast-properties@2.0.0(transitive)