Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: syntax-tree/estree-util-attach-comments
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.1.0
Choose a base ref
...
head repository: syntax-tree/estree-util-attach-comments
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2.1.1
Choose a head ref
  • 10 commits
  • 7 files changed
  • 1 contributor

Commits on Jan 21, 2023

  1. Update dev-dependencies

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    a88f8c6 View commit details
  2. Update Actions

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    2d94735 View commit details
  3. Update dev-dependencies

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    cc019fd View commit details
  4. Update tsconfig.json

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    e990f9f View commit details
  5. Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    ff4a456 View commit details
  6. Refactor code-style

    *   Add more docs to JSDoc
    *   Add support for `null` in input of API types
    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    4e6a846 View commit details
  7. Use Node test runner

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    90d21f9 View commit details
  8. Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    73e8c43 View commit details
  9. Add improved docs

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    ad0241b View commit details
  10. 2.1.1

    wooorm committed Jan 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    wooorm Titus
    Copy the full SHA
    d8fd933 View commit details
Showing with 255 additions and 196 deletions.
  1. +4 −4 .github/workflows/main.yml
  2. +1 −147 index.js
  3. +191 −0 lib/index.js
  4. +8 −9 package.json
  5. +14 −5 readme.md
  6. +28 −22 test.js
  7. +9 −9 tsconfig.json
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -7,15 +7,15 @@ jobs:
name: ${{matrix.node}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dcodeIO/setup-node-nvm@master
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{matrix.node}}
- run: npm install
- run: npm test
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
strategy:
matrix:
node:
- lts/fermium
- lts/gallium
- node
148 changes: 1 addition & 147 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,147 +1 @@
/**
* @typedef {import('estree').BaseNode} EstreeNode
* @typedef {import('estree').Comment} EstreeComment
*
* @typedef State
* @property {Array<EstreeComment>} comments
* @property {number} index
*
* @typedef Fields
* @property {boolean} leading
* @property {boolean} trailing
*/

const own = {}.hasOwnProperty

/**
* Attach semistandard estree comment nodes to the tree.
*
* @param {EstreeNode} tree
* @param {Array<EstreeComment>} [comments]
*/
export function attachComments(tree, comments) {
const list = (comments || []).concat().sort(compare)
if (list.length > 0) walk(tree, {comments: list, index: 0})
return tree
}

/**
* Attach semistandard estree comment nodes to the tree.
*
* @param {EstreeNode} node
* @param {State} state
*/
function walk(node, state) {
// Done, we can quit.
if (state.index === state.comments.length) {
return
}

/** @type {Array<EstreeNode>} */
const children = []
/** @type {Array<EstreeComment>} */
const comments = []
/** @type {string} */
let key

// Find all children of `node`
for (key in node) {
if (own.call(node, key)) {
/** @type {EstreeNode|Array<EstreeNode>} */
// @ts-expect-error: indexable.
const value = node[key]

// Ignore comments.
if (value && typeof value === 'object' && key !== 'comments') {
if (Array.isArray(value)) {
let index = -1

while (++index < value.length) {
if (value[index] && typeof value[index].type === 'string') {
children.push(value[index])
}
}
} else if (typeof value.type === 'string') {
children.push(value)
}
}
}
}

// Sort the children.
children.sort(compare)

// Initial comments.
comments.push(...slice(state, node, false, {leading: true, trailing: false}))

let index = -1

while (++index < children.length) {
walk(children[index], state)
}

// Dangling or trailing comments.
comments.push(
...slice(state, node, true, {
leading: false,
trailing: children.length > 0
})
)

if (comments.length > 0) {
// @ts-expect-error, yes, because they’re nonstandard.
node.comments = comments
}
}

/**
* @param {State} state
* @param {EstreeNode} node
* @param {boolean} compareEnd
* @param {Fields} fields
*/
function slice(state, node, compareEnd, fields) {
/** @type {Array<EstreeComment>} */
const result = []

while (
state.comments[state.index] &&
compare(state.comments[state.index], node, compareEnd) < 1
) {
result.push(Object.assign({}, state.comments[state.index++], fields))
}

return result
}

/**
* @param {EstreeNode|EstreeComment} left
* @param {EstreeNode|EstreeComment} right
* @param {boolean} [compareEnd]
* @returns {number}
*/
function compare(left, right, compareEnd) {
const field = compareEnd ? 'end' : 'start'

// Offsets.
if (left.range && right.range) {
return left.range[0] - right.range[compareEnd ? 1 : 0]
}

// Points.
if (left.loc && left.loc.start && right.loc && right.loc[field]) {
return (
left.loc.start.line - right.loc[field].line ||
left.loc.start.column - right.loc[field].column
)
}

// Just `start` (and `end`) on nodes.
// Default in most parsers.
if ('start' in left && field in right) {
// @ts-expect-error Added by Acorn
return left.start - right[field]
}

return Number.NaN
}
export {attachComments} from './lib/index.js'
191 changes: 191 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
* @typedef {import('estree').BaseNode} EstreeNode
* @typedef {import('estree').Comment} EstreeComment
*
* @typedef State
* Info passed around.
* @property {Array<EstreeComment>} comments
* Comments.
* @property {number} index
* Index of comment.
*
* @typedef Fields
* @property {boolean} leading
* @property {boolean} trailing
*/

const own = {}.hasOwnProperty

/**
* Attach semistandard estree comment nodes to the tree.
*
* This mutates the given `tree`.
* It takes `comments`, walks the tree, and adds comments as close as possible
* to where they originated.
*
* Comment nodes are given two boolean fields: `leading` (`true` for
* `/* a *\/ b`) and `trailing` (`true` for `a /* b *\/`).
* Both fields are `false` for dangling comments: `[/* a *\/]`.
* This is what `recast` uses too, and is somewhat similar to Babel, which is
* not estree but instead uses `leadingComments`, `trailingComments`, and
* `innerComments` arrays on nodes.
*
* The algorithm checks any node: even recent (or future) proposals or
* nonstandard syntax such as JSX, because it ducktypes to find nodes instead
* of having a list of visitor keys.
*
* The algorithm supports `loc` fields (line/column), `range` fields (offsets),
* and direct `start` / `end` fields.
*
* @template {EstreeNode} Tree
* Node type.
* @param {Tree} tree
* Tree to attach to.
* @param {Array<EstreeComment> | null | undefined} [comments]
* List of comments.
* @returns {Tree}
* Given tree.
*/
// To do: next major: don’t return given `tree`.
export function attachComments(tree, comments) {
const list = (comments || []).concat().sort(compare)
if (list.length > 0) walk(tree, {comments: list, index: 0})
return tree
}

/**
* Attach semistandard estree comment nodes to the tree.
*
* @param {EstreeNode} node
* Node.
* @param {State} state
* Info passed around.
* @returns {void}
* Nothing.
*/
function walk(node, state) {
// Done, we can quit.
if (state.index === state.comments.length) {
return
}

/** @type {Array<EstreeNode>} */
const children = []
/** @type {Array<EstreeComment>} */
const comments = []
/** @type {string} */
let key

// Find all children of `node`
for (key in node) {
if (own.call(node, key)) {
/** @type {EstreeNode | Array<EstreeNode>} */
// @ts-expect-error: indexable.
const value = node[key]

// Ignore comments.
if (value && typeof value === 'object' && key !== 'comments') {
if (Array.isArray(value)) {
let index = -1

while (++index < value.length) {
if (value[index] && typeof value[index].type === 'string') {
children.push(value[index])
}
}
} else if (typeof value.type === 'string') {
children.push(value)
}
}
}
}

// Sort the children.
children.sort(compare)

// Initial comments.
comments.push(...slice(state, node, false, {leading: true, trailing: false}))

let index = -1

while (++index < children.length) {
walk(children[index], state)
}

// Dangling or trailing comments.
comments.push(
...slice(state, node, true, {
leading: false,
trailing: children.length > 0
})
)

if (comments.length > 0) {
// @ts-expect-error, yes, because they’re nonstandard.
node.comments = comments
}
}

/**
* @param {State} state
* Info passed around.
* @param {EstreeNode} node
* Node.
* @param {boolean} compareEnd
* Whether to compare on the end (default is on start).
* @param {Fields} fields
* Fields.
* @returns {Array<EstreeComment>}
* Slice from `state.comments`.
*/
function slice(state, node, compareEnd, fields) {
/** @type {Array<EstreeComment>} */
const result = []

while (
state.comments[state.index] &&
compare(state.comments[state.index], node, compareEnd) < 1
) {
result.push(Object.assign({}, state.comments[state.index++], fields))
}

return result
}

/**
* Sort two nodes (or comments).
*
* @param {EstreeNode | EstreeComment} left
* A node.
* @param {EstreeNode | EstreeComment} right
* The other node.
* @param {boolean | undefined} [compareEnd=false]
* Compare on `end` of `right`, default is to compare on `start`.
* @returns {number}
* Sorting.
*/
function compare(left, right, compareEnd) {
const field = compareEnd ? 'end' : 'start'

// Offsets.
if (left.range && right.range) {
return left.range[0] - right.range[compareEnd ? 1 : 0]
}

// Points.
if (left.loc && left.loc.start && right.loc && right.loc[field]) {
return (
left.loc.start.line - right.loc[field].line ||
left.loc.start.column - right.loc[field].column
)
}

// Just `start` (and `end`) on nodes.
// Default in most parsers.
if ('start' in left && field in right) {
// @ts-expect-error Added by Acorn
return left.start - right[field]
}

return Number.NaN
}
Loading