New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

esm-analyzer

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

esm-analyzer

The scanner and analyzer of ESM.

latest
Source
npmnpm
Version
0.3.6
Version published
Maintainers
1
Created
Source

esm-analyzer

NPM Version NPM Downloads License

The scanner and analyzer of ESM.

Installation

pnpm i esm-analyzer

Scanner

The scanner uses @babel/parser to parse the source code and find the import and export statements.

import { scan } from 'esm-analyzer'

const { imports, exports } = scan(sourceCode, lang)

lang

The lang parameter is used to specify the language of the source code.

It can be one of the following values:

  • js
  • jsx
  • ts
  • tsx

imports scanner

cases

  • ✅ import default, e.g. import foo from 'bar'
  • ✅ import namespace, e.g. import * as foo from 'bar'
  • ✅ import named, e.g. import { foo } from 'bar'
  • ✅ import named with alias, e.g. import { foo as bar } from 'bar'
  • ✅ import type named, e.g. import type { foo } from 'bar' or import { type foo } from 'bar'

type definition

The imports type is defined as follows:

interface ScanResultBase {
  source: string
  loc: ASTNodeLocation
}

// import a from 'a'
interface ScanImportResultDefault extends ScanResultBase {
  type: 'default'
  local: string // a
}

// import * as a from 'a'
interface ScanImportResultNamespace extends ScanResultBase {
  type: 'namespace'
  local: string // a
}

// import { a as b } from 'a'
interface ScanImportResultImport extends ScanResultBase {
  type: 'import'
  subType: 'id' | 'string' // id: `import { a } from 'a'`; string: `import { 'a' } from 'a'`
  isType: boolean // `import type { a } from 'a'` or `import { type a } from 'a'`
  local: string // b
  imported: string // a
}

type ScanImportResultItem = ScanImportResultDefault | ScanImportResultNamespace | ScanImportResultImport

The imports is an array of ScanImportResultItem.

examples

The basic example:

const code = 'import foo from "bar"'
scan(code, 'js').imports

will be:

[
  {
    loc: {
      end: {
        column: 10,
        index: 10,
        line: 1,
      },
      start: {
        column: 7,
        index: 7,
        line: 1,
      },
    },
    local: 'foo',
    source: 'bar',
    type: 'default',
  },
]

the standalone API

Also, you can use the standalone import scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanImport(node))

config

The scanImport function accepts a config object as the second parameter:

interface ScanImportConfig {
  includeSource?: string[] // the source list to be included
  excludeSource?: string[] // the source list to be excluded
  skipType?: boolean // whether to skip the type import
}

const defaultConfig: Required<ScanImportConfig> = {
  includeSource: [],
  excludeSource: [],
  skipType: false,
}

variable declarations scanner

The variable declarations is an array of ScanVariableDeclarationResult.

cases

  • ❌ deferred init
  • primitive declaration
    • StringLiteral
    • NumericLiteral
    • BooleanLiteral
    • NullLiteral
  • ✅ reference declaration
  • complex declaration
    • ObjectExpression
    • ArrayExpression
    • CallExpression
    • ❗ Others are not supported yet

type definition

The ScanVariableDeclarationResult is defined as follows:

export interface ScanVariableDeclarationResult {
  loc: ASTNodeLocation
  kind: t.VariableDeclaration['kind']
  name: string
  init: ResolveVariableDeclaration
}

examples

The basic example:

const code = 'const foo = "bar"'
scan(code, 'js').variables

The output will be:

[
  {
    init: {
      type: 'StringLiteral',
      value: 'bar',
    },
    kind: 'const',
    loc: {
      end: {
        column: 17,
        index: 17,
        line: 1,
      },
      start: {
        column: 6,
        index: 6,
        line: 1,
      },
    },
    name: 'foo',
  },
]

the standalone API

Also, you can use the standalone variable scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanVariableDeclaration(node))

config

The scanVariableDeclaration function accepts a config object as the second parameter:

export type VariableType =
  | 'StringLiteral'
  | 'NumericLiteral'
  | 'BooleanLiteral'
  | 'NullLiteral'
  | 'ObjectExpression'
  | 'ArrayExpression'
  | 'CallExpression'

interface ScanVariableDeclarationConfig {
  includeType?: VariableType[]
  excludeType?: VariableType[]
}

export scanner

cases

  • ✅ export default, e.g. export default foo
  • ✅ export named, e.g. export { foo }
    • only support Identifier and primitive
    • functions are not supported yet
  • ✅ export named with alias, e.g. export { foo as bar }
  • ✅ export all, e.g. export * from 'foo'
  • ❌ export type, e.g. export type { foo } from 'bar'
  • ❌ export type named, e.g. export { type foo } from 'bar'

type definition

The exports type is defined as follows, will return ScanExportResult[]

export interface ScanExportNamedDeclarationResult {
  type: 'ExportNamedDeclaration'
  subType: 'VariableDeclaration'
  kind: t.VariableDeclaration['kind']
  declarations: {
    name: string
    init: ResolveVariableDeclaration
  }[]
}

export interface ScanExportNamedSpecifiersResult {
  type: 'ExportNamedDeclaration'
  subType: 'Specifiers'
  specifiers: {
    local: string
    exported: string
  }[]
  source: string | null
}

export interface ScanExportAllResult {
  type: 'ExportAllDeclaration'
  source: string
}

export interface ScanExportDefaultIdentifierResult {
  type: 'ExportDefaultDeclaration'
  subType: 'Identifier'
  id: string
}

export interface ScanExportDefaultObjectResult {
  type: 'ExportDefaultDeclaration'
  subType: 'ObjectExpression'
  properties: {
    key: string
    value: ResolveVariableDeclaration
  }[]
}

export type ScanExportResult = (
  | ScanExportNamedDeclarationResult
  | ScanExportNamedSpecifiersResult
  | ScanExportAllResult
  | ScanExportDefaultIdentifierResult
  | ScanExportDefaultObjectResult
) & {
  loc: ASTNodeLocation
}

examples

The basic example:

const code = 'export default { a: 1, b: 2 }'
scan(code, 'js').exports

The result will be:

[
  {
    loc: {
      end: {
        column: 7,
        index: 58,
        line: 5,
      },
      start: {
        column: 6,
        index: 7,
        line: 2,
      },
    },
    properties: [
      {
        key: 'a',
        value: {
          type: 'NumericLiteral',
          value: 1,
        },
      },
      {
        key: 'b',
        value: {
          type: 'NumericLiteral',
          value: 2,
        },
      },
    ],
    subType: 'ObjectExpression',
    type: 'ExportDefaultDeclaration',
  },
]

the standalone API

Also, you can use the standalone export scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanExport(node))

config

The scanExport function accepts a config object as the second parameter:

export type ScanExportType =
  | 'ExportNamedDeclaration'
  | 'ExportAllDeclaration'
  | 'ExportDefaultDeclaration'

interface ScanExportConfig {
  includeType?: ScanExportType[]
  excludeType?: ScanExportType[]
}

Analyzer

use analyze you can find all the variable declarations and their import statement and export statement

const code1 = {
  filename: '/src/bar.js',
  code: `
      export const bar = 'bar'
    `,
}
const code2 = {
  filename: '/src/foo.js',
  code: `
      import { bar, ref } from './bar'
      export const foo = bar
      const foo2 = ref(1)
    `,
}
const p = new Project('test')
p.addFile(code1.filename, code1.code)
p.addFile(code2.filename, code2.code)
await p.prepare()
const c = p.findAnalyzeResults(code2.filename)
expect(c).toMatchSnapshot()

The result is map, and it's entries is:

[
  [
    {
      init: {
        id: 'bar',
        type: 'Identifier',
      },
      kind: 'const',
      loc: {
        end: {
          column: 28,
          index: 68,
          line: 3,
        },
        start: {
          column: 19,
          index: 59,
          line: 3,
        },
      },
      name: 'foo',
    },
    {
      fromExport: {
        declarations: [
          {
            init: {
              type: 'StringLiteral',
              value: 'bar',
            },
            name: 'bar',
          },
        ],
        kind: 'const',
        loc: {
          end: {
            column: 30,
            index: 31,
            line: 2,
          },
          start: {
            column: 6,
            index: 7,
            line: 2,
          },
        },
        subType: 'VariableDeclaration',
        type: 'ExportNamedDeclaration',
      },
      fromImport: {
        imported: 'bar',
        isType: false,
        loc: {
          end: {
            column: 18,
            index: 19,
            line: 2,
          },
          start: {
            column: 15,
            index: 16,
            line: 2,
          },
        },
        local: 'bar',
        source: './bar',
        subType: 'id',
        type: 'import',
      },
      id: 'bar',
      importFile: '/src/bar.js',
      type: 'Identifier',
    },
  ],
  [
    {
      init: {
        arguments: [
          {
            type: 'NumericLiteral',
            value: 1,
          },
        ],
        callee: 'ref',
        type: 'CallExpression',
      },
      kind: 'const',
      loc: {
        end: {
          column: 25,
          index: 94,
          line: 4,
        },
        start: {
          column: 12,
          index: 81,
          line: 4,
        },
      },
      name: 'foo2',
    },
    {
      arguments: [
        {
          type: 'NumericLiteral',
          value: 1,
        },
      ],
      callee: 'ref',
      calleeFrom: {
        imported: 'ref',
        isType: false,
        loc: {
          end: {
            column: 23,
            index: 24,
            line: 2,
          },
          start: {
            column: 20,
            index: 21,
            line: 2,
          },
        },
        local: 'ref',
        source: './bar',
        subType: 'id',
        type: 'import',
      },
      type: 'CallExpression',
    },
  ],
]

License

MIT

Keywords

esm

FAQs

Package last updated on 20 Nov 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts