Comparing version 1.0.1 to 1.1.0
{ | ||
"name": "flast", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Flatten JS AST", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"lint": "eslint .", | ||
"prepare": "husky install", | ||
@@ -8,0 +9,0 @@ "test": "node tests/allTester.js" |
@@ -137,3 +137,3 @@ # flAST - FLat Abstract Syntax Tree | ||
const {generateFlatAST, generateCode} = require('flast'); | ||
const ast = generateFlatAST(`console.log('flAST'`); | ||
const ast = generateFlatAST(`console.log('flAST')`); | ||
const reconstructedCode = generateCode(ast[0]); // rebuild from root node | ||
@@ -189,2 +189,8 @@ ``` | ||
## Contribution | ||
To contribute to this project see our [contribution guide](CONTRIBUTING.md) | ||
To contribute to this project see our [contribution guide](CONTRIBUTING.md) | ||
## Changes | ||
### v1.1.0 | ||
- Added parentKey property. | ||
- Improved ASTNode definition (useful for intellisense). | ||
- Ability to pass options directly to the espree parser. |
// noinspection JSUnusedGlobalSymbols | ||
// eslint-disable-next-line no-unused-vars | ||
const {parse, ASTNode} = require('espree'); | ||
const {parse, ASTNode:espreeASTNode} = require('espree'); | ||
const {generate} = require('escodegen'); | ||
const estraverse = require('estraverse'); | ||
const eslineScope = require('eslint-scope'); | ||
// eslint-disable-next-line no-unused-vars | ||
const {analyze, ScopeManager} = require('eslint-scope'); | ||
const ecmaVersion = 2022; | ||
const ecmaVersion = 'latest'; | ||
/** | ||
* @param inputCode | ||
* @typedef ASTNode | ||
* @property {number} nodeId | ||
* @property {string} src | ||
* @property {array} childNodes | ||
* @property {?ASTNode} parentNode | ||
* @property {ScopeManager} scope | ||
* @property {?string} parentKey | ||
*/ | ||
const ASTNode = espreeASTNode; | ||
/** | ||
* @param {string} inputCode | ||
* @param {object} opts Additional options for espree | ||
* @return {ASTNode} The root of the AST | ||
*/ | ||
function parseCode(inputCode) { | ||
return parse(inputCode, {ecmaVersion, comment: true, range: true}); | ||
function parseCode(inputCode, opts = {}) { | ||
// noinspection JSValidateTypes | ||
return parse(inputCode, {ecmaVersion, comment: true, range: true, ...opts}); | ||
} | ||
/** | ||
* Return the key the child node is assigned in the parent node if applicable; null otherwise. | ||
* @param {ASTNode} parent | ||
* @param {number} targetChildNodeId | ||
* @returns {string|null} | ||
*/ | ||
function getParentKey(parent, targetChildNodeId) { | ||
if (parent) { | ||
for (const key of Object.keys(parent)) { | ||
if (parent[key]?.nodeId === targetChildNodeId) return key; | ||
} | ||
} | ||
return null; | ||
} | ||
const generateFlatASTDefaultOptions = { | ||
detailed: true, // If false, include only original node without any further details | ||
includeSrc: true, // If false, do not include node src. Only available when `detailed` option is true | ||
parseOpts: { // Options for the espree parser | ||
sourceType: 'module', | ||
}, | ||
}; | ||
@@ -30,10 +62,11 @@ | ||
function generateFlatAST(inputCode, opts = {}) { | ||
opts = Object.assign(Object.assign({}, generateFlatASTDefaultOptions), opts); | ||
const rootNode = parseCode(inputCode); | ||
opts = { ...generateFlatASTDefaultOptions, ...opts }; | ||
const parseOpts = opts.parseOpts || {}; | ||
const rootNode = parseCode(inputCode, parseOpts); | ||
let scopeManager; | ||
try { | ||
if (opts.detailed) { // noinspection JSCheckFunctionSignatures | ||
scopeManager = eslineScope.analyze(rootNode, {optimistic: true, ecmaVersion}); | ||
scopeManager = analyze(rootNode, {optimistic: true, ecmaVersion}); | ||
} | ||
} catch (e) {} | ||
} catch {} | ||
const tree = []; | ||
@@ -44,3 +77,3 @@ let nodeId = 0; | ||
if (opts.detailed) { // noinspection JSCheckFunctionSignatures | ||
currentScope = scopeManager ? scopeManager.acquire(rootNode) : {}; | ||
currentScope = scopeManager?.acquire(rootNode) ?? {}; | ||
} | ||
@@ -54,2 +87,3 @@ estraverse.traverse(rootNode, { | ||
node.parentNode = parentNode; | ||
node.parentKey = getParentKey(parentNode, node.nodeId); | ||
// Set new scope when entering a function structure | ||
@@ -102,4 +136,3 @@ if (scopeManager && /Function/.test(node.type)) currentScope = scopeManager.acquire(node); | ||
function generateCode(rootNode, opts = {}) { | ||
opts = Object.assign(Object.assign({}, generateCodeDefaultOptions), opts); | ||
return generate(rootNode, opts); | ||
return generate(rootNode, { ...generateCodeDefaultOptions, ...opts }); | ||
} | ||
@@ -113,2 +146,2 @@ | ||
ASTNode, | ||
}; | ||
}; |
@@ -19,5 +19,5 @@ const assert = require('assert'); | ||
}; | ||
const expectedNubmerOfNodes = 11; | ||
assert(ast.length === expectedNubmerOfNodes, | ||
`Unexpected number of nodes: Expected ${expectedNubmerOfNodes} but got ${ast.length}`); | ||
const expectedNumberOfNodes = 11; | ||
assert(ast.length === expectedNumberOfNodes, | ||
`Unexpected number of nodes: Expected ${expectedNumberOfNodes} but got ${ast.length}`); | ||
for (const nodeType of Object.keys(expectedBreakdown)) { | ||
@@ -41,7 +41,50 @@ const numberOfNodes = ast.filter(n => n.type === nodeType).length; | ||
function testFlastOptionsDetailed() { | ||
const code = `var a = [1]; a[0];`; | ||
const noDetailsAst = generateFlatAST(code, {detailed: false, includeSrc: true}); // includeSrc will be ignored | ||
const [noDetailsVarDec, noDetailsVarRef] = noDetailsAst.filter(n => n.type === 'Identifier'); | ||
assert(!( | ||
noDetailsVarDec.parentNode || noDetailsVarDec.childNodes || noDetailsVarDec.references || | ||
noDetailsVarRef.declNode || noDetailsVarRef.nodeId || noDetailsVarRef.scope || noDetailsVarRef.src), | ||
`Flat AST generated with details despite 'detailed' option set to false.`); | ||
const detailedAst = generateFlatAST(code, {detailed: true}); | ||
const [detailedVarDec, detailedVarRef] = detailedAst.filter(n => n.type === 'Identifier'); | ||
assert( | ||
detailedVarDec.parentNode && detailedVarDec.childNodes && detailedVarDec.references && | ||
detailedVarRef.declNode && detailedVarRef.nodeId && detailedVarRef.scope && detailedVarRef.src, | ||
`Flat AST missing details despite 'detailed' option set to true.`); | ||
const detailedNoSrcAst = generateFlatAST(code, {detailed: true, includeSrc: false}); | ||
assert(!detailedNoSrcAst[0].src, | ||
`Flat AST includes details despite 'detailed' option set to true and 'includeSrc' option set to false.`); | ||
} | ||
/** | ||
* Verify the code breakdown generates the expected nodes by checking the properties of the generated ASTNodes. | ||
*/ | ||
function testNodesStructureIntegrity() { | ||
const code = `a=3`; | ||
const ast = generateFlatAST(code); | ||
const expectedBreakdown = [ | ||
{nodeId: 0, type: 'Program', start: 0, end: 3, src: 'a=3', parentNode: null, parentKey: null}, | ||
{nodeId: 1, type: 'ExpressionStatement', start: 0, end: 3, src: 'a=3', parentKey: null}, | ||
{nodeId: 2, type: 'AssignmentExpression', start: 0, end: 3, src: 'a=3', operator: '=', parentKey: 'expression'}, | ||
{nodeId: 3, type: 'Identifier', start: 0, end: 1, src: 'a', parentKey: 'left'}, | ||
{nodeId: 4, type: 'Literal', start: 2, end: 3, src: '3', value: 3, raw: '3', parentKey: 'right'}, | ||
]; | ||
expectedBreakdown.forEach(node => { | ||
const parsedNode = ast[node.nodeId]; | ||
for (const [k, v] of Object.entries(node)) { | ||
assert(parsedNode[k] === v, | ||
`Value in parsed node, ${parsedNode[k]}, does not match expected value: ${v}, for key ${k}`); | ||
} | ||
}); | ||
} | ||
const tests = { | ||
'number of nodes': testNumberOfNodes, | ||
'parse and generate': testParseAndGenerate, | ||
'options: detailed': testFlastOptionsDetailed, | ||
'ASTNode structure integrity': testNodesStructureIntegrity, | ||
}; | ||
module.exports = tests; |
Sorry, the diff of this file is not supported yet
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
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
30516
14
441
194