Socket
Socket
Sign inDemoInstall

@contrast/rewriter

Package Overview
Dependencies
Maintainers
15
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contrast/rewriter - npm Package Compare versions

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"
}
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc