Skip to content

loreanvictor/affe

Repository files navigation

npm package minimized gzipped size npm GitHub Workflow Status

Query any object with CSS-like selectors. Particularly useful for querying ASTs of programming languages.

import { js, pipe, select, all } from 'affe'


const config = await readFile('eslint.config.js', 'utf8')

//
// 👇 Let's find out which eslint rules are specified
//    in the config.
//
const rules = await pipe(
  // 👇 parse the config as JS to AST
  js(config),

  // 👇 select linting rules
  select(`
    export property[name=rules]
    > value > object > property > key > *
  `),

  // 👇 get all of the names of the rules
  all(),
)

console.log(rules)
// > semi
// > prefer-const

Contents


Installation

Node:

npm i affe

Browser / Deno:

import { js } from 'https://esm.sh/affe'

Usage

affe transforms objects to a DOM-inspired format, so you can query them with CSS-like selectors. This can be paired with parsers of differnt languages to conveniently inspect code written in said language.

affe provides out-of-the-box support for JS/JSX (using espree):

import { jsx, pipe, select, pick, all } from 'affe'

const code = jsx`
  export default ({ name, style }) => (
    <div className={style}>Hello, {name}!</div>
  )
`

//
// 👇 Let's find out the name of the properties
//    of the exported component.
//
const params = pipe(
  code,
  select('export params property key *'),
  pick(node => node.name ?? node.value),
  all(),
)

console.log(params)
// > [name, style]

👉 Use tag to create a language tag for any other language:

import * as csstree from 'css-tree'
import { tag } from 'affe'


export const css = tag({
  parse: csstree.parse,
})

Which can then be used like this:

import { css } from './css'
import { pipe, select, pick, all } from 'affe'


const code = css`
  .foo {
    color: red;
  }

  .bar {
    color: blue;
  }
`

const classes = await pipe(
  code,
  select('ClassSelector'),
  pick(node => node.name),
  all(),
)

console.log(classess)
// > [ foo, bar ]

👉 Use query to inspect any object:

import { pipe, query, select, pick, first } from 'affe'

const graph = {
  vertices: [
    {
      id: 'a',
      label: 'Node A',
      edges: [
        { to: 'a', label: 'loopback' },
        { to: 'b' }
      ]
    },
    {
      id: 'b',
      label: 'Node B',
      edges: [
        { to: 'a' }
      ]
    }
  ]
}


// 👇 lets find a node with a labeled edge
const label = await pipe(
  query(() => graph),
  select('node:has(>edges [label])'),
  pick(node => node.label),
  first(),
)

console.log(label)
// > Node A

// 👇 now lets find nodes with an
//    unlabeled edge going to them
const ids = await pipe(
  query(() => graph),
  select('edges :not([label])'),
  pick(node => node.to),
  all(),
)

console.log(ids)
// > [ a, b ]

Contribution

You need node, NPM to start and git to start.

# clone the code
git clone git@github.com:loreanvictor/affe.git
# install stuff
npm i

Make sure all checks are successful on your PRs. This includes all tests passing, high code coverage, correct typings and abiding all the linting rules. The code is typed with TypeScript, Jest is used for testing and coverage reports, ESLint and TypeScript ESLint are used for linting. Subsequently, IDE integrations for TypeScript and ESLint would make your life much easier (for example, VSCode supports TypeScript out of the box and has this nice ESLint plugin), but you could also use the following commands:

# run tests
npm test
# check code coverage
npm run coverage
# run linter
npm run lint
# run type checker
npm run typecheck

Releases

No releases published

Packages

No packages published