11'use strict'
22
3- /*
4- This rewriter is basically a JavaScript version of Orchestrion-JS. The goal is
5- not to replace Orchestrion-JS, but rather to make it easier and faster to write
6- new integrations in the short-term, especially as many changes to the rewriter
7- will be needed as all the patterns we need have not been identified yet. This
8- will avoid the back and forth of having to make Rust changes to an external
9- library for every integration change or addition that requires something new.
10-
11- In the meantime, we'll work concurrently on a change to Orchestrion-JS that
12- adds an "arbitrary transform" or "plugin" system that can be used from
13- JavaScript, in order to enable quick iteration while still using Orchestrion-JS.
14- Once that's done we'll use that, so that we can remove this JS approach and
15- return to using Orchestrion-JS.
16-
17- The long term goal is to backport any additional features we add to the JS
18- rewriter (or using the plugin system in Orchestrion-JS once we're using that)
19- to Orchestrion-JS once we're confident that the implementation is fairly
20- complete and has all features we need.
21-
22- Here is a list of the additions and changes in this rewriter compared to
23- Orchestrion-JS that will need to be backported:
24-
25- (NOTE: Please keep this list up-to-date whenever new features are added)
26-
27- - Supports an `astQuery` field to filter AST nodes with an esquery query. This
28- is mostly meant to be used when experimenting or if what needs to be queried
29- is not a function. We'll see over time if something like this is needed to be
30- backported or if it can be replaced by simpler queries.
31- - Supports replacing methods of child class instances in the base constructor.
32- - Supports tracing iterator (sync/async) returning functions (sync/async).
33- */
34-
353const { readFileSync } = require ( 'fs' )
364const { join } = require ( 'path' )
37- const semifies = require ( '../../../../../vendor/dist/semifies' )
385const log = require ( '../../../../dd-trace/src/log' )
39- const { getEnvironmentVariable } = require ( '../../../../dd-trace/src/config/helper' )
40- const { transform } = require ( './transformer' )
41- const { generate, parse, traverse } = require ( './compiler' )
426const instrumentations = require ( './instrumentations' )
7+ const { create } = require ( './orchestrion' )
438
44- const NODE_OPTIONS = getEnvironmentVariable ( 'NODE_OPTIONS' )
45-
46- /** @type {Record<string, Set<string>> } map of module base name to supported function query versions */
47- const supported = { }
9+ /** @type {Record<string, string> } map of module base name to version */
10+ const moduleVersions = { }
4811const disabled = new Set ( )
49-
50- // TODO: Source maps without `--enable-source-maps`.
51- const enableSourceMaps = NODE_OPTIONS ?. includes ( '--enable-source-maps' ) ||
52- process . execArgv ?. some ( arg => arg . includes ( '--enable-source-maps' ) )
53-
54- let SourceMapGenerator
12+ const matcher = create ( instrumentations , 'dc-polyfill' )
5513
5614function rewrite ( content , filename , format ) {
5715 if ( ! content ) return content
16+ if ( ! filename . includes ( 'node_modules' ) ) return content
5817
59- const sourceType = format === 'module' ? 'module' : 'script'
60-
61- try {
62- let ast
63-
64- filename = filename . replace ( 'file://' , '' )
65-
66- for ( const inst of instrumentations ) {
67- const { astQuery, functionQuery = { } , module : { name, versionRange, filePath } } = inst
18+ filename = filename . replace ( 'file://' , '' )
6819
69- if ( disabled . has ( name ) ) continue
70- if ( ! filename . endsWith ( `${ name } /${ filePath } ` ) ) continue
71- if ( ! satisfies ( filename , filePath , versionRange ) ) continue
20+ const moduleType = format === 'module' ? 'esm' : 'cjs'
21+ const [ modulePath ] = filename . split ( '/node_modules/' ) . reverse ( )
22+ const moduleParts = modulePath . split ( '/' )
23+ const splitIndex = moduleParts [ 0 ] . startsWith ( '@' ) ? 2 : 1
24+ const moduleName = moduleParts . slice ( 0 , splitIndex ) . join ( '/' )
25+ const filePath = moduleParts . slice ( splitIndex ) . join ( '/' )
26+ const version = getVersion ( filename , filePath )
7227
73- ast ??= parse ( content . toString ( ) , { range : true , sourceType } )
28+ if ( disabled . has ( moduleName ) ) return content
7429
75- const query = astQuery || fromFunctionQuery ( functionQuery )
76- const state = { ...inst , sourceType, functionQuery }
30+ const transformer = matcher . getTransformer ( moduleName , version , filePath )
7731
78- traverse ( ast , query , ( ...args ) => transform ( state , ...args ) )
79- }
32+ if ( ! transformer ) return content
8033
81- if ( ast ) {
82- if ( ! enableSourceMaps ) return generate ( ast )
34+ try {
35+ // TODO: pass existing sourcemap as input for remapping
36+ const { code, map } = transformer . transform ( content , moduleType )
8337
84- // TODO: Can we use the same version of `source-map` that DI uses?
85- SourceMapGenerator ??= require ( '../../../../../vendor/dist/@datadog/source-map' ) . SourceMapGenerator
38+ if ( ! map ) return code
8639
87- const sourceMap = new SourceMapGenerator ( { file : filename } )
88- const code = generate ( ast , { sourceMap } )
89- const map = Buffer . from ( sourceMap . toString ( ) ) . toString ( 'base64' )
40+ const inlineMap = Buffer . from ( map ) . toString ( 'base64' )
9041
91- return code + '\n' + `//# sourceMappingURL=data:application/json;base64,${ map } `
92- }
42+ return code + '\n' + `//# sourceMappingURL=data:application/json;base64,${ inlineMap } `
9343 } catch ( e ) {
9444 log . error ( e )
9545 }
@@ -101,55 +51,20 @@ function disable (instrumentation) {
10151 disabled . add ( instrumentation )
10252}
10353
104- function satisfies ( filename , filePath , versions ) {
54+ function getVersion ( filename , filePath ) {
10555 const [ basename ] = filename . split ( filePath )
10656
107- supported [ basename ] ??= new Set ( )
108-
109- if ( ! supported [ basename ] . has ( versions ) ) {
57+ if ( ! moduleVersions [ basename ] ) {
11058 try {
11159 const pkg = JSON . parse ( readFileSync (
11260 join ( basename , 'package.json' ) , 'utf8'
11361 ) )
11462
115- if ( semifies ( pkg . version , versions ) ) {
116- supported [ basename ] . add ( versions )
117- }
63+ moduleVersions [ basename ] = pkg . version
11864 } catch { }
11965 }
12066
121- return supported [ basename ] . has ( versions )
122- }
123-
124- // TODO: Support index
125- function fromFunctionQuery ( functionQuery ) {
126- const { methodName, functionName, expressionName, className } = functionQuery
127- const queries = [ ]
128-
129- if ( className ) {
130- queries . push (
131- `[id.name="${ className } "]` ,
132- `[id.name="${ className } "] > ClassExpression` ,
133- `[id.name="${ className } "] > ClassBody > [key.name="${ methodName } "] > [async]` ,
134- `[id.name="${ className } "] > ClassExpression > ClassBody > [key.name="${ methodName } "] > [async]`
135- )
136- } else if ( methodName ) {
137- queries . push (
138- `ClassBody > [key.name="${ methodName } "] > [async]` ,
139- `Property[key.name="${ methodName } "] > [async]`
140- )
141- }
142-
143- if ( functionName ) {
144- queries . push ( `FunctionDeclaration[id.name="${ functionName } "][async]` )
145- } else if ( expressionName ) {
146- queries . push (
147- `FunctionExpression[id.name="${ expressionName } "][async]` ,
148- `ArrowFunctionExpression[id.name="${ expressionName } "][async]`
149- )
150- }
151-
152- return queries . join ( ', ' )
67+ return moduleVersions [ basename ]
15368}
15469
15570module . exports = { rewrite, disable }
0 commit comments