0.3.6 • Published 6 months ago

esm-analyzer v0.3.6

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

esm-analyzer

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

0.3.6

6 months ago

0.3.5

9 months ago

0.3.4

9 months ago

0.3.3

9 months ago

0.3.2

9 months ago

0.3.1

9 months ago

0.3.0

9 months ago

0.2.6

9 months ago

0.2.5

9 months ago

0.2.4

9 months ago

0.2.3

9 months ago

0.2.2

9 months ago

0.2.1

9 months ago

0.2.0

9 months ago

0.1.3

10 months ago

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago

0.0.3

10 months ago

0.0.2

10 months ago

0.0.1

10 months ago