aspects | | Cell 1 | Search

This code defines a system for inserting debugging information into JavaScript code by modifying its Abstract Syntax Tree (AST) and injecting calls to a logging function.

Run example

npm run import -- "inspect every statement"

inspect every statement

var importer = require('../Core')
var {transpile} = importer.import("transpile code")
var {selectAst, makeExpr} = importer.import("select code tree")
var {htmlToTree} = importer.import("html to tree")

var STATEMENTS = `//*[contains(@type, "Declaration")]
|//*[contains(@type, "Statement")]`
var NEAR_IDENTIFIERS = `
 ./Identifier/@name
|./*/Identifier/@name
|./*/*/Identifier/@name
|./*/*/*/Identifier/@name

|./parent::*/Identifier/@name
|./parent::*/*/Identifier/@name
|./parent::*/*/*/Identifier/@name
|./parent::*/*/*/*/Identifier/@name

|./parent::*/parent::*/Identifier/@name
|./parent::*/parent::*/*/Identifier/@name
|./parent::*/parent::*/*/*/Identifier/@name
|./parent::*/parent::*/*/*/*/Identifier/@name

`

function inspectCallback(ctx) {
    console.log(JSON.stringify(ctx))
}

function inspectTemplate(ctx) {
    // code inserted in to transpiled module
    inspectCallback(ctx)
}

function insertInspect(filename, code, ctx) {
    var inspect = makeExpr(inspectTemplate)

    // replace line with the line number from original range
    var range = JSON.parse(ctx.getAttribute('range'))
    var line = code.substr(0, range[0]).split('\n').length
    
    // replace the ctx with nearby identifiers
    // TODO: 'replace' transpiler command
    var replaceCtx = selectAst(`.//Identifier[@name="ctx"]`, inspect)
    var nearbyIdentifiers = selectAst([NEAR_IDENTIFIERS], ctx)
    var nearbyCtx = makeExpr(`{
${nearbyIdentifiers.join(',')},
//ctx: ${JSON.stringify(htmlToTree(ctx))},
type: "${ctx.getAttribute('type')}",
line: ${line},
filename: "${filename}"
}`)
    nearbyCtx.setAttribute('parent-attr', 'arguments')
    replaceCtx.replaceWith(nearbyCtx)
    
    // insert into parent statement body
    // TODO: make this a transpile operation because it contains node calls
    var parent = selectAst(`./parent::*`, ctx)
    Array.from(inspect.childNodes).forEach(n => {
        if(n.setAttribute) {
            n.setAttribute('parent-attr', 'body')
        }
        parent.insertBefore(n, ctx)
    })
}

function transpileInspect(code, filename) {
    return transpile([
        [STATEMENTS, insertInspect.bind(null, filename, code)]
    ], code)
}

module.exports = {
    inspectCallback,
    inspectTemplate,
    transpileInspect,
}

What the code could have been:

const { importCoreFunctions } = require('../Core');

/**
 * Import necessary functions from the 'Core' module.
 */
const { transpile, selectAst, makeExpr, htmlToTree } = importCoreFunctions([
  'transpile code',
 'select code tree',
  'html to tree',
]);

/**
 * XPath expressions for selecting specific nodes in the code tree.
 */
const STATEMENTS = `//*[contains(@type, "Declaration") or contains(@type, "Statement")]`;
const NEAR_IDENTIFIERS = `
 ./Identifier/@name
  |./*/Identifier/@name
  |./*/*/Identifier/@name
  |./*/*/*/Identifier/@name

  |./parent::*/Identifier/@name
  |./parent::*/*/Identifier/@name
  |./parent::*/*/*/Identifier/@name
  |./parent::*/*/*/*/Identifier/@name

  |./parent::*/parent::*/Identifier/@name
  |./parent::*/parent::*/*/Identifier/@name
  |./parent::*/parent::*/*/*/Identifier/@name
  |./parent::*/parent::*/*/*/*/Identifier/@name
`;

/**
 * Inspect callback function.
 *
 * @param {Object} ctx - The context object.
 */
function inspectCallback(ctx) {
  console.log(JSON.stringify(ctx));
}

/**
 * Inspect template function.
 *
 * @param {Object} ctx - The context object.
 */
function inspectTemplate(ctx) {
  // Code inserted into the transpiled module.
  inspectCallback(ctx);
}

/**
 * Inserts inspection code into the given code snippet.
 *
 * @param {string} filename - The filename of the code snippet.
 * @param {string} code - The code snippet.
 * @param {Element} ctx - The context element.
 */
function insertInspect(filename, code, ctx) {
  const inspect = makeExpr(inspectTemplate);

  // Get the line number from the original range.
  const range = JSON.parse(ctx.getAttribute('range'));
  const line = code
   .substr(0, range[0])
   .split('\n')
   .length;

  // Get nearby identifiers.
  const nearbyIdentifiers = selectAst(NEAR_IDENTIFIERS, ctx).map((id) => id.getAttribute('name'));

  // Create the nearby context object.
  const nearbyCtx = makeExpr({
    type: ctx.getAttribute('type'),
    line,
    filename,
   ...nearbyIdentifiers.reduce((acc, id) => ({...acc, [id]: true }), {}),
  });

  // Replace the context element with the nearby context element.
  const replaceCtx = selectAst(`.//Identifier[@name="ctx"]`, inspect);
  replaceCtx.replaceWith(nearbyCtx);

  // Insert the inspection code into the parent statement body.
  const parent = selectAst('./parent::*/*', ctx);
  Array.from(inspect.childNodes).forEach((n) => {
    if (n.setAttribute) {
      n.setAttribute('parent-attr', 'body');
    }
    parent.insertBefore(n, ctx);
  });
}

/**
 * Transpiles the code snippet and inserts inspection code.
 *
 * @param {string} code - The code snippet.
 * @param {string} filename - The filename of the code snippet.
 * @returns {string} The transpiled code.
 */
function transpileInspect(code, filename) {
  return transpile([['/Statements', insertInspect.bind(null, filename, code)]], code);
}

module.exports = {
  inspectCallback,
  inspectTemplate,
  transpileInspect,
};

This code defines a function transpileInspect that aims to modify JavaScript code by inserting debugging information into it.

Here's a breakdown:

  1. Imports:

  2. Constants:

  3. Helper Functions:

  4. insertInspect Function:

  5. transpileInspect Function (Incomplete):

In essence, this code provides a mechanism to instrument JavaScript code with debugging information by inserting calls to a logging function at specific points within the code. The insertInspect function handles the logic of finding the relevant context and constructing the debugging information.