Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Aug 10, 2021
1 parent f526b48 commit 889cf54
Show file tree
Hide file tree
Showing 16 changed files with 355 additions and 89 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
coverage/
node_modules/
.DS_Store
*.d.ts
*.log
yarn.lock
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @typedef {import('./lib/index.js').Options} Options
*/

import remarkUsage from './lib/index.js'

export default remarkUsage
18 changes: 14 additions & 4 deletions lib/generate/config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('vfile').VFile} VFile
* @typedef {import('type-fest').PackageJson} PackageJson
* @typedef {import('../index.js').Options} Options
*/

import url from 'url'
import {resolve} from 'import-meta-resolve'
import {nanoid} from 'nanoid'
import {relativeModule} from '../util/relative-module.js'

/** @param {{tree: Root, file: VFile, options: Options, pkg: PackageJson|undefined, pkgRoot: string, main?: string|undefined, name?: string|undefined, id?: string}} ctx */
export async function config(ctx) {
const options = ctx.options
const pkg = ctx.pkg || {}
const cwd = ctx.file.cwd
const mainRoot = url.pathToFileURL(options.main ? cwd : ctx.pkgRoot || cwd)
const mainId = relativeModule(options.main || pkg.main || 'index.js')
/** @type {string|undefined} */
let main

try {
main = await resolve(mainId, mainRoot.href + '/')
main = await resolve(
mainId,
url.pathToFileURL(options.main ? cwd : ctx.pkgRoot || cwd).href + '/'
)
} catch {}

ctx.cwd = cwd
ctx.main = main
ctx.name = options.name || pkg.name || null
ctx.name = options.name || pkg.name || undefined
ctx.id = 'remark-usage-example-' + nanoid().toLowerCase()
}
33 changes: 23 additions & 10 deletions lib/generate/find-example.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import url from 'url'
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('vfile').VFile} VFile
* @typedef {import('type-fest').PackageJson} PackageJson
* @typedef {import('../index.js').Options} Options
*/

import {URL, pathToFileURL} from 'url'
import {promises as fs} from 'fs'
import {resolve} from 'import-meta-resolve'
import {relativeModule} from '../util/relative-module.js'

/** @param {{tree: Root, file: VFile, options: Options, pkg: PackageJson, pkgRoot: string, main: string|undefined, name: string|undefined, id: string, exampleFileUrl?: string, example?: string}} ctx */
export async function findExample(ctx) {
const fn = ctx.options.example ? findExplicitExample : findImplicitExample
const fileUrl = await fn(ctx)
const fileUrl = await (ctx.options.example
? findExplicitExample(ctx.file.cwd, ctx.options.example)
: findImplicitExample(ctx.file.cwd))

if (!fileUrl) {
throw new Error('Could not find example')
Expand All @@ -19,15 +28,19 @@ export async function findExample(ctx) {
example.charAt(example.length - 1) === '\n' ? example : example + '\n'
}

function findExplicitExample(ctx) {
return resolve(
relativeModule(ctx.options.example),
url.pathToFileURL(ctx.cwd).href + '/'
)
/**
* @param {string} cwd
* @param {string} example
*/
function findExplicitExample(cwd, example) {
return resolve(relativeModule(example), pathToFileURL(cwd).href + '/')
}

async function findImplicitExample(ctx) {
const from = url.pathToFileURL(ctx.cwd).href + '/'
/**
* @param {string} cwd
*/
async function findImplicitExample(cwd) {
const from = pathToFileURL(cwd).href + '/'
const promises = [
'./example.js',
'./example/index.js',
Expand Down
9 changes: 9 additions & 0 deletions lib/generate/find-package.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('vfile').VFile} VFile
* @typedef {import('type-fest').PackageJson} PackageJson
* @typedef {import('../index.js').Options} Options
*/

import {findUpOne} from 'vfile-find-up'
import {read} from 'to-vfile'
import path from 'path'

/** @param {{tree: Root, file: VFile, options: Options, pkg?: PackageJson, pkgRoot?: string}} ctx */
export async function findPackage(ctx) {
const file = ctx.file
const pkgFile = await findUpOne(
Expand All @@ -21,6 +29,7 @@ export async function findPackage(ctx) {
}
}

/** @type {PackageJson} */
let pkg

try {
Expand Down
108 changes: 77 additions & 31 deletions lib/generate/generate.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,143 @@
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('mdast').BlockContent} BlockContent
* @typedef {import('vfile').VFile} VFile
* @typedef {import('type-fest').PackageJson} PackageJson
* @typedef {import('../index.js').Options} Options
* @typedef {import('./instrument.js').Log} Log
* @typedef {import('./instrument.js').Reference} Reference
* @typedef {import('./tokenize.js').Token} Token
* @typedef {import('./tokenize.js').Comment} Comment
* @typedef {import('./tokenize.js').Code} Code
*
* @typedef {{type: 'code', values: string[]}} CodeWithValues
*/

import {unified} from 'unified'
import remarkParse from 'remark-parse'
import {removePosition} from 'unist-util-remove-position'

const processor = unified().use(remarkParse)

/** @param {{tree: Root, file: VFile, options: Options, pkg: PackageJson, pkgRoot: string, main: string|undefined, name: string|undefined, id: string, exampleFileUrl: string, example: string, exampleInstrumented: string, logs: Log[], mainReferences: Reference[], exampleInstrumentedPath: string, tokens: Token[], nodes?: BlockContent[]}} ctx */
export function generate(ctx) {
/** @type {BlockContent[]} */
const nodes = []
let index = -1

while (++index < ctx.tokens.length) {
const node = ctx.tokens[index]
const fn =
node.type === 'comment' ? comment : node.type === 'code' ? code : () => {}

nodes.push(...(fn(node, ctx) || []))
const token = ctx.tokens[index]
const result =
token.type === 'comment'
? comment(token)
: token.type === 'code'
? code(token, ctx)
: []

nodes.push(...result)
}

ctx.nodes = nodes
}

function comment(node) {
const tree = removePosition(processor.parse(node.values.join('')))
/**
* @param {Comment} token
* @returns {BlockContent[]}
*/
function comment(token) {
const tree = removePosition(processor.parse(token.values.join('')))
// @ts-expect-error: Assume block content in `root`.
return tree.children
}

function code(node, ctx) {
/**
* @param {Code} token
* @param {{tree: Root, file: VFile, options: Options, pkg: PackageJson, pkgRoot: string, main: string|undefined, name: string|undefined, id: string, exampleFileUrl: string, example: string, exampleInstrumented: string, logs: Log[], mainReferences: Reference[], exampleInstrumentedPath: string, tokens: Token[]}} ctx
* @returns {BlockContent[]}
*/
function code(token, ctx) {
const example = ctx.example
/** @type {Array.<CodeWithValues|Log>} */
const tokens = []
let start = node.start
let token
let start = token.start
/** @type {CodeWithValues|Log|undefined} */
let tok

while (start < node.end) {
while (start < token.end) {
let lineEnd = example.indexOf('\n', start)
lineEnd = lineEnd === -1 || lineEnd >= node.end ? node.end : lineEnd
lineEnd = lineEnd === -1 || lineEnd >= token.end ? token.end : lineEnd

const consoleCall = findInLine(ctx.logs, start, lineEnd)

if (consoleCall && consoleCall.values.length > 0) {
if (token && token === consoleCall) {
if (tok && tok === consoleCall) {
// Ignore: it’s the same multiline console call.
} else {
token = consoleCall
tokens.push(token)
tok = consoleCall
tokens.push(tok)
}
} else {
const mainReference =
ctx.name && findInLine(ctx.mainReferences, start, lineEnd)

const line = mainReference
? example.slice(start, mainReference.start) +
mainReference.quote +
ctx.name +
mainReference.quote +
example.slice(mainReference.end, lineEnd)
: example.slice(start, lineEnd)

if (!token || token.type !== 'code') {
token = {type: 'code', values: []}
tokens.push(token)
const line =
mainReference &&
typeof mainReference.start === 'number' &&
typeof mainReference.end === 'number'
? example.slice(start, mainReference.start) +
mainReference.quote +
ctx.name +
mainReference.quote +
example.slice(mainReference.end, lineEnd)
: example.slice(start, lineEnd)

if (!tok || !('type' in tok) || tok.type !== 'code') {
tok = {type: 'code', values: []}
tokens.push(tok)
}

token.values.push(line)
tok.values.push(line)
}

start = lineEnd + 1
}

/** @type {BlockContent[]} */
const nodes = []
let index = -1

while (++index < tokens.length) {
const token = tokens[index]
nodes.push({
type: 'code',
lang: token.type === 'code' ? 'javascript' : token.language,
lang: 'language' in token ? token.language : 'javascript',
value: token.values.join('\n').replace(/^\n+|\n+$/g, '')
})
}

return nodes
}

/**
* @template {Log|Reference} Value
* @param {Value[]} values
* @param {number} start
* @param {number} end
* @returns {Value|undefined}
*/
function findInLine(values, start, end) {
let index = -1

while (++index < values.length) {
const reference = values[index]

if (
typeof reference.start === 'number' &&
typeof reference.end === 'number' &&
// Reference in:
(reference.start >= start && reference.end <= end) ||
// Line in reference:
(start >= reference.start && end <= reference.end)
((reference.start >= start && reference.end <= end) ||
// Line in reference:
(start >= reference.start && end <= reference.end))
) {
return reference
}
Expand Down
Loading

0 comments on commit 889cf54

Please sign in to comment.