2021-01-07 15:00:04 -08:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
// @ts-check
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const md = require ( '../markdown' ) ;
2022-11-19 11:26:11 -08:00
const docs = require ( './documentation' ) ;
2021-01-07 15:00:04 -08:00
/** @typedef {import('../markdown').MarkdownNode} MarkdownNode */
2022-09-27 08:27:23 -08:00
/** @typedef {import('../markdown').MarkdownHeaderNode} MarkdownHeaderNode */
/** @typedef {import('../markdown').MarkdownLiNode} MarkdownLiNode */
/** @typedef {import('../markdown').MarkdownTextNode} MarkdownTextNode */
2021-01-07 15:00:04 -08:00
class ApiParser {
/ * *
* @ param { string } apiDir
2021-07-22 14:47:12 -07:00
* @ param { string = } paramsPath
2021-01-07 15:00:04 -08:00
* /
2021-07-22 14:47:12 -07:00
constructor ( apiDir , paramsPath ) {
2021-01-07 15:00:04 -08:00
let bodyParts = [ ] ;
for ( const name of fs . readdirSync ( apiDir ) ) {
2021-07-22 11:01:18 -07:00
if ( ! name . endsWith ( '.md' ) )
continue ;
2021-01-07 15:00:04 -08:00
if ( name === 'params.md' )
paramsPath = path . join ( apiDir , name ) ;
2021-01-07 16:12:25 -08:00
else
bodyParts . push ( fs . readFileSync ( path . join ( apiDir , name ) ) . toString ( ) ) ;
2021-01-07 15:00:04 -08:00
}
const body = md . parse ( bodyParts . join ( '\n' ) ) ;
2022-09-27 08:27:23 -08:00
const params = paramsPath ? md . parse ( fs . readFileSync ( paramsPath ) . toString ( ) ) : undefined ;
2022-03-02 12:43:16 -08:00
checkNoDuplicateParamEntries ( params ) ;
2021-01-07 15:00:04 -08:00
const api = params ? applyTemplates ( body , params ) : body ;
2022-11-23 08:40:47 -08:00
/** @type {Map<string, docs.Class>} */
2021-01-07 15:00:04 -08:00
this . classes = new Map ( ) ;
md . visitAll ( api , node => {
if ( node . type === 'h1' )
this . parseClass ( node ) ;
2021-02-01 11:43:26 -08:00
} ) ;
md . visitAll ( api , node => {
2021-01-07 15:00:04 -08:00
if ( node . type === 'h2' )
this . parseMember ( node ) ;
2021-02-01 11:43:26 -08:00
} ) ;
md . visitAll ( api , node => {
2021-01-07 15:00:04 -08:00
if ( node . type === 'h3' )
this . parseArgument ( node ) ;
} ) ;
2022-11-19 11:26:11 -08:00
this . documentation = new docs . Documentation ( [ ... this . classes . values ( ) ] ) ;
2021-01-07 15:00:04 -08:00
this . documentation . index ( ) ;
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } node
2021-01-07 15:00:04 -08:00
* /
parseClass ( node ) {
let extendsName = null ;
const name = node . text . substring ( 'class: ' . length ) ;
for ( const member of node . children ) {
if ( member . type . startsWith ( 'h' ) )
continue ;
2021-01-07 16:12:25 -08:00
if ( member . type === 'li' && member . liType === 'bullet' && member . text . startsWith ( 'extends: [' ) ) {
2021-01-07 15:00:04 -08:00
extendsName = member . text . substring ( 'extends: [' . length , member . text . indexOf ( ']' ) ) ;
continue ;
}
}
2024-05-20 10:30:32 -07:00
const metainfo = extractMetainfo ( node ) ;
const clazz = new docs . Class ( metainfo , name , [ ] , extendsName , extractComments ( node ) ) ;
if ( metainfo . hidden )
return ;
2021-01-07 15:00:04 -08:00
this . classes . set ( clazz . name , clazz ) ;
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2021-01-07 15:00:04 -08:00
* /
parseMember ( spec ) {
2022-04-07 18:51:05 -07:00
const match = spec . text . match ( /(event|method|property|async method|optional method|optional async method): ([^.]+)\.(.*)/ ) ;
2021-01-10 21:00:52 -08:00
if ( ! match )
throw new Error ( 'Invalid member: ' + spec . text ) ;
2024-07-15 16:35:15 +02:00
const metainfo = extractMetainfo ( spec ) ;
2021-01-07 15:00:04 -08:00
const name = match [ 3 ] ;
let returnType = null ;
2022-04-06 19:02:10 -07:00
let optional = false ;
2021-01-07 15:00:04 -08:00
for ( const item of spec . children || [ ] ) {
2022-04-06 19:02:10 -07:00
if ( item . type === 'li' && item . liType === 'default' ) {
2024-07-15 16:35:15 +02:00
const parsed = this . parseType ( item , metainfo . since ? ? 'v1.0' ) ;
2022-04-06 19:02:10 -07:00
returnType = parsed . type ;
optional = parsed . optional ;
}
2021-01-07 15:00:04 -08:00
}
if ( ! returnType )
2022-11-19 11:26:11 -08:00
returnType = new docs . Type ( 'void' ) ;
2021-01-07 15:00:04 -08:00
2022-04-06 13:36:20 -07:00
const comments = extractComments ( spec ) ;
2021-01-07 15:00:04 -08:00
let member ;
if ( match [ 1 ] === 'event' )
2024-05-20 10:30:32 -07:00
member = docs . Member . createEvent ( metainfo , name , returnType , comments ) ;
2021-01-07 15:00:04 -08:00
if ( match [ 1 ] === 'property' )
2024-05-20 10:30:32 -07:00
member = docs . Member . createProperty ( metainfo , name , returnType , comments , ! optional ) ;
2022-04-07 18:51:05 -07:00
if ( [ 'method' , 'async method' , 'optional method' , 'optional async method' ] . includes ( match [ 1 ] ) ) {
2024-05-20 10:30:32 -07:00
member = docs . Member . createMethod ( metainfo , name , [ ] , returnType , comments ) ;
2022-04-07 18:51:05 -07:00
if ( match [ 1 ] . includes ( 'async' ) )
2021-01-07 23:37:53 -08:00
member . async = true ;
2022-04-07 18:51:05 -07:00
if ( match [ 1 ] . includes ( 'optional' ) )
member . required = false ;
2021-01-07 23:37:53 -08:00
}
2022-09-27 08:27:23 -08:00
if ( ! member )
throw new Error ( 'Unknown member: ' + spec . text ) ;
2022-11-23 08:40:47 -08:00
const clazz = /** @type {docs.Class} */ ( this . classes . get ( match [ 2 ] ) ) ;
2024-05-20 10:30:32 -07:00
if ( ! clazz )
throw new Error ( ` Unknown class ${ match [ 2 ] } for member: ` + spec . text ) ;
if ( metainfo . hidden )
return ;
2021-02-09 12:11:48 -08:00
const existingMember = clazz . membersArray . find ( m => m . name === name && m . kind === member . kind ) ;
2021-02-11 23:43:59 -08:00
if ( existingMember && isTypeOverride ( existingMember , member ) ) {
2022-09-27 08:27:23 -08:00
for ( const lang of member ? . langs ? . only || [ ] ) {
2021-01-15 16:01:41 -08:00
existingMember . langs . types = existingMember . langs . types || { } ;
existingMember . langs . types [ lang ] = returnType ;
}
} else {
clazz . membersArray . push ( member ) ;
}
2021-01-07 15:00:04 -08:00
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2021-01-07 15:00:04 -08:00
* /
parseArgument ( spec ) {
2021-07-22 11:01:18 -07:00
const match = spec . text . match ( /(param|option): (.*)/ ) ;
if ( ! match )
2021-01-29 20:08:22 +01:00
throw ` Something went wrong with matching ${ spec . text } ` ;
2021-07-22 11:01:18 -07:00
// For "test.describe.only.title":
// - className is "test"
// - methodName is "describe.only"
// - argument name is "title"
const parts = match [ 2 ] . split ( '.' ) ;
const className = parts [ 0 ] ;
const name = parts [ parts . length - 1 ] ;
const methodName = parts . slice ( 1 , parts . length - 1 ) . join ( '.' ) ;
const clazz = this . classes . get ( className ) ;
2021-01-10 21:00:52 -08:00
if ( ! clazz )
2021-07-22 11:01:18 -07:00
throw new Error ( 'Invalid class ' + className ) ;
2021-08-27 21:57:40 -07:00
const method = clazz . membersArray . find ( m => m . kind === 'method' && m . name === methodName ) ;
2021-01-10 21:00:52 -08:00
if ( ! method )
2021-07-22 11:01:18 -07:00
throw new Error ( ` Invalid method ${ className } . ${ methodName } when parsing: ${ match [ 0 ] } ` ) ;
2021-01-28 17:51:41 -08:00
if ( ! name )
throw new Error ( 'Invalid member name ' + spec . text ) ;
2021-01-07 15:00:04 -08:00
if ( match [ 1 ] === 'param' ) {
2024-09-26 06:31:42 -07:00
const arg = this . parseProperty ( spec , match [ 2 ] ) ;
2024-05-20 10:30:32 -07:00
if ( ! arg )
return ;
2021-01-28 17:51:41 -08:00
arg . name = name ;
const existingArg = method . argsArray . find ( m => m . name === arg . name ) ;
2021-02-11 23:43:59 -08:00
if ( existingArg && isTypeOverride ( existingArg , arg ) ) {
2021-02-01 11:43:26 -08:00
if ( ! arg . langs || ! arg . langs . only )
throw new Error ( 'Override does not have lang: ' + spec . text ) ;
2021-01-28 17:51:41 -08:00
for ( const lang of arg . langs . only ) {
existingArg . langs . overrides = existingArg . langs . overrides || { } ;
existingArg . langs . overrides [ lang ] = arg ;
}
} else {
method . argsArray . push ( arg ) ;
}
2021-01-07 15:00:04 -08:00
} else {
2022-04-14 13:22:42 -07:00
// match[1] === 'option'
2024-09-26 06:31:42 -07:00
const p = this . parseProperty ( spec , match [ 2 ] ) ;
2024-05-20 10:30:32 -07:00
if ( ! p )
return ;
2021-01-07 15:00:04 -08:00
let options = method . argsArray . find ( o => o . name === 'options' ) ;
if ( ! options ) {
2022-11-19 11:26:11 -08:00
const type = new docs . Type ( 'Object' , [ ] ) ;
2024-07-15 16:35:15 +02:00
options = docs . Member . createProperty ( { langs : { } , since : method . since , deprecated : undefined , discouraged : undefined } , 'options' , type , undefined , false ) ;
2021-01-07 15:00:04 -08:00
method . argsArray . push ( options ) ;
}
p . required = false ;
2024-09-24 02:51:09 -07:00
options . type ? . properties ? . push ( p ) ;
2021-01-07 15:00:04 -08:00
}
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2024-09-26 06:31:42 -07:00
* @ param { string } memberName
2024-05-20 10:30:32 -07:00
* @ returns { docs . Member | null }
2021-01-07 15:00:04 -08:00
* /
2024-09-26 06:31:42 -07:00
parseProperty ( spec , memberName ) {
2021-01-07 16:12:25 -08:00
const param = childrenWithoutProperties ( spec ) [ 0 ] ;
2022-09-27 08:27:23 -08:00
const text = /** @type {string}*/ ( param . text ) ;
2024-09-26 06:31:42 -07:00
if ( text . substring ( text . lastIndexOf ( '>' ) + 1 ) . trim ( ) )
throw new Error ( ` Extra information after type while processing " ${ memberName } ". \n You probably need an extra empty line before the description. \n ================ \n ${ text } ` ) ;
2022-04-06 19:02:10 -07:00
let typeStart = text . indexOf ( '<' ) ;
2022-04-14 13:22:42 -07:00
while ( '?e' . includes ( text [ typeStart - 1 ] ) )
2022-04-06 19:02:10 -07:00
typeStart -- ;
const name = text . substring ( 0 , typeStart ) . replace ( /\`/g , '' ) . trim ( ) ;
2021-01-07 15:00:04 -08:00
const comments = extractComments ( spec ) ;
2024-05-20 10:30:32 -07:00
const metainfo = extractMetainfo ( spec ) ;
if ( metainfo . hidden )
return null ;
2024-07-15 16:35:15 +02:00
const { type , optional } = this . parseType ( /** @type {MarkdownLiNode} */ ( param ) , metainfo . since ? ? 'v1.0' ) ;
2024-05-20 10:30:32 -07:00
return docs . Member . createProperty ( metainfo , name , type , comments , ! optional ) ;
2021-01-07 15:00:04 -08:00
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownLiNode } spec
2024-07-15 16:35:15 +02:00
* @ param { string } since
2024-05-20 10:30:32 -07:00
* @ return { { type : docs . Type , optional : boolean } }
2021-01-07 15:00:04 -08:00
* /
2024-07-15 16:35:15 +02:00
parseType ( spec , since ) {
2021-01-07 16:12:25 -08:00
const arg = parseVariable ( spec . text ) ;
2021-01-07 15:00:04 -08:00
const properties = [ ] ;
2022-09-27 08:27:23 -08:00
for ( const child of /** @type {MarkdownLiNode[]} */ ( spec . children ) || [ ] ) {
const { name , text } = parseVariable ( /** @type {string} */ ( child . text ) ) ;
2021-01-07 15:00:04 -08:00
const comments = /** @type {MarkdownNode[]} */ ( [ { type : 'text' , text } ] ) ;
2024-07-15 16:35:15 +02:00
const childType = this . parseType ( child , since ) ;
properties . push ( docs . Member . createProperty ( { langs : { } , since , deprecated : undefined , discouraged : undefined } , name , childType . type , comments , ! childType . optional ) ) ;
2021-01-07 15:00:04 -08:00
}
2022-11-19 11:26:11 -08:00
const type = docs . Type . parse ( arg . type , properties ) ;
2024-05-20 10:30:32 -07:00
return { type , optional : arg . optional } ;
2021-01-07 15:00:04 -08:00
}
}
/ * *
2021-02-25 22:22:47 -08:00
* @ param { string } line
2024-05-20 10:30:32 -07:00
* @ returns { { name : string , type : string , text : string , optional : boolean } }
2021-01-07 15:00:04 -08:00
* /
2021-01-07 16:12:25 -08:00
function parseVariable ( line ) {
2021-01-07 15:00:04 -08:00
let match = line . match ( /^`([^`]+)` (.*)/ ) ;
if ( ! match )
match = line . match ( /^(returns): (.*)/ ) ;
if ( ! match )
match = line . match ( /^(type): (.*)/ ) ;
2021-02-25 22:22:47 -08:00
if ( ! match )
match = line . match ( /^(argument): (.*)/ ) ;
2021-01-07 15:00:04 -08:00
if ( ! match )
throw new Error ( 'Invalid argument: ' + line ) ;
const name = match [ 1 ] ;
2022-04-06 19:02:10 -07:00
let remainder = match [ 2 ] ;
let optional = false ;
2024-05-20 10:30:32 -07:00
while ( '?' . includes ( remainder [ 0 ] ) ) {
2022-04-14 13:22:42 -07:00
if ( remainder [ 0 ] === '?' )
optional = true ;
2022-04-06 19:02:10 -07:00
remainder = remainder . substring ( 1 ) ;
}
2021-01-07 15:00:04 -08:00
if ( ! remainder . startsWith ( '<' ) )
2021-07-22 11:01:18 -07:00
throw new Error ( ` Bad argument: " ${ name } " in " ${ line } " ` ) ;
2021-01-07 15:00:04 -08:00
let depth = 0 ;
for ( let i = 0 ; i < remainder . length ; ++ i ) {
const c = remainder . charAt ( i ) ;
if ( c === '<' )
++ depth ;
if ( c === '>' )
-- depth ;
if ( depth === 0 )
2024-05-20 10:30:32 -07:00
return { name , type : remainder . substring ( 1 , i ) , text : remainder . substring ( i + 2 ) , optional } ;
2021-01-07 15:00:04 -08:00
}
2022-09-28 18:45:01 -07:00
throw new Error ( 'Should not be reached, line: ' + line ) ;
2021-01-07 15:00:04 -08:00
}
/ * *
* @ param { MarkdownNode [ ] } body
* @ param { MarkdownNode [ ] } params
* /
function applyTemplates ( body , params ) {
const paramsMap = new Map ( ) ;
for ( const node of params )
paramsMap . set ( '%%-' + node . text + '-%%' , node ) ;
const visit = ( node , parent ) => {
if ( node . text && node . text . includes ( '-inline- = %%' ) ) {
const [ name , key ] = node . text . split ( '-inline- = ' ) ;
const list = paramsMap . get ( key ) ;
2021-04-30 01:16:09 -03:00
const newChildren = [ ] ;
2021-01-07 15:00:04 -08:00
if ( ! list )
throw new Error ( 'Bad template: ' + key ) ;
for ( const prop of list . children ) {
const template = paramsMap . get ( prop . text ) ;
if ( ! template )
throw new Error ( 'Bad template: ' + prop . text ) ;
2021-01-07 16:12:25 -08:00
const children = childrenWithoutProperties ( template ) ;
2022-09-27 08:27:23 -08:00
const { name : argName } = parseVariable ( children [ 0 ] . text || '' ) ;
2021-04-30 01:16:09 -03:00
newChildren . push ( {
2021-01-07 15:00:04 -08:00
type : node . type ,
text : name + argName ,
2022-07-05 16:24:50 -08:00
children : [ ... node . children , ... template . children . map ( c => md . clone ( c ) ) ]
2021-01-07 15:00:04 -08:00
} ) ;
}
2021-04-30 01:16:09 -03:00
const nodeIndex = parent . children . indexOf ( node ) ;
parent . children = [ ... parent . children . slice ( 0 , nodeIndex ) , ... newChildren , ... parent . children . slice ( nodeIndex + 1 ) ] ;
2021-01-07 15:00:04 -08:00
} else if ( node . text && node . text . includes ( ' = %%' ) ) {
const [ name , key ] = node . text . split ( ' = ' ) ;
node . text = name ;
const template = paramsMap . get ( key ) ;
if ( ! template )
throw new Error ( 'Bad template: ' + key ) ;
2023-08-10 14:48:26 -07:00
// Insert right after all metadata options like "* since",
// keeping any additional text like **Usage** below the template.
let index = node . children . findIndex ( child => child . type !== 'li' ) ;
if ( index === - 1 )
index = 0 ;
node . children . splice ( index , 0 , ... template . children . map ( c => md . clone ( c ) ) ) ;
2022-09-27 09:29:34 -08:00
} else if ( node . text && node . text . includes ( '%%-template-' ) ) {
node . text . replace ( /%%-template-[^%]+-%%/ , templateName => {
const template = paramsMap . get ( templateName ) ;
if ( ! template )
throw new Error ( 'Bad template: ' + templateName ) ;
const nodeIndex = parent . children . indexOf ( node ) ;
parent . children = [ ... parent . children . slice ( 0 , nodeIndex ) , ... template . children , ... parent . children . slice ( nodeIndex + 1 ) ] ;
} ) ;
2021-01-07 15:00:04 -08:00
}
for ( const child of node . children || [ ] )
visit ( child , node ) ;
if ( node . children )
node . children = node . children . filter ( child => ! child . text || ! child . text . includes ( '-inline- = %%' ) ) ;
} ;
for ( const node of body )
visit ( node , null ) ;
return body ;
}
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } item
2021-01-07 15:00:04 -08:00
* @ returns { MarkdownNode [ ] }
* /
function extractComments ( item ) {
2022-04-13 16:13:30 -07:00
return childrenWithoutProperties ( item ) . filter ( c => {
2021-01-07 15:00:04 -08:00
if ( c . type . startsWith ( 'h' ) )
return false ;
if ( c . type === 'li' && c . liType === 'default' )
return false ;
return true ;
} ) ;
}
/ * *
* @ param { string } apiDir
2021-07-22 14:47:12 -07:00
* @ param { string = } paramsPath
2021-01-07 15:00:04 -08:00
* /
2021-07-22 14:47:12 -07:00
function parseApi ( apiDir , paramsPath ) {
return new ApiParser ( apiDir , paramsPath ) . documentation ;
2021-01-07 15:00:04 -08:00
}
2022-07-05 16:24:50 -08:00
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2024-05-20 10:30:32 -07:00
* @ returns { import ( './documentation' ) . Metainfo & { hidden : boolean } }
2022-07-05 16:24:50 -08:00
* /
function extractMetainfo ( spec ) {
return {
langs : extractLangs ( spec ) ,
since : extractSince ( spec ) ,
2022-11-23 08:40:47 -08:00
deprecated : extractAttribute ( spec , 'deprecated' ) ,
discouraged : extractAttribute ( spec , 'discouraged' ) ,
2024-05-20 10:30:32 -07:00
hidden : extractHidden ( spec ) ,
2022-07-05 16:24:50 -08:00
} ;
}
2021-01-07 16:12:25 -08:00
/ * *
* @ param { MarkdownNode } spec
2021-01-08 15:00:14 -08:00
* @ returns { import ( './documentation' ) . Langs }
2021-01-07 16:12:25 -08:00
* /
function extractLangs ( spec ) {
2022-09-27 08:27:23 -08:00
for ( const child of spec . children || [ ] ) {
2021-01-08 15:00:14 -08:00
if ( child . type !== 'li' || child . liType !== 'bullet' || ! child . text . startsWith ( 'langs:' ) )
continue ;
const only = child . text . substring ( 'langs:' . length ) . trim ( ) ;
/** @type {Object<string, string>} */
const aliases = { } ;
for ( const p of child . children || [ ] ) {
2022-09-27 08:27:23 -08:00
const match = /** @type {string}*/ ( p . text ) . match ( /alias-(\w+)[\s]*:(.*)/ ) ;
2021-01-08 15:00:14 -08:00
if ( match )
aliases [ match [ 1 ] . trim ( ) ] = match [ 2 ] . trim ( ) ;
}
return {
2021-02-01 11:13:13 -08:00
only : only ? only . split ( ',' ) . map ( l => l . trim ( ) ) : undefined ,
2021-01-15 16:01:41 -08:00
aliases ,
2021-01-28 17:51:41 -08:00
types : { } ,
overrides : { }
2021-01-08 15:00:14 -08:00
} ;
}
return { } ;
2021-01-07 16:12:25 -08:00
}
2022-07-05 16:24:50 -08:00
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2022-07-05 16:24:50 -08:00
* @ returns { string }
* /
function extractSince ( spec ) {
for ( const child of spec . children ) {
if ( child . type !== 'li' || child . liType !== 'bullet' || ! child . text . startsWith ( 'since:' ) )
continue ;
return child . text . substring ( child . text . indexOf ( ':' ) + 1 ) . trim ( ) ;
}
console . error ( 'Missing since: v1.** declaration in node:' ) ;
console . error ( spec ) ;
process . exit ( 1 ) ;
}
2022-04-13 16:13:30 -07:00
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2022-04-13 16:13:30 -07:00
* @ returns { boolean }
* /
2024-05-20 10:30:32 -07:00
function extractHidden ( spec ) {
2022-04-13 16:13:30 -07:00
for ( const child of spec . children ) {
2024-05-20 10:30:32 -07:00
if ( child . type === 'li' && child . liType === 'bullet' && child . text === 'hidden' )
2022-04-13 16:13:30 -07:00
return true ;
}
return false ;
}
2022-11-23 08:40:47 -08:00
/ * *
* @ param { MarkdownHeaderNode } spec
* @ param { string } name
* @ returns { string | undefined }
* /
function extractAttribute ( spec , name ) {
for ( const child of spec . children ) {
if ( child . type !== 'li' || child . liType !== 'bullet' || ! child . text . startsWith ( name + ':' ) )
continue ;
return child . text . substring ( child . text . indexOf ( ':' ) + 1 ) . trim ( ) || undefined ;
}
}
2021-01-07 16:12:25 -08:00
/ * *
2022-09-27 08:27:23 -08:00
* @ param { MarkdownHeaderNode } spec
2021-01-07 16:12:25 -08:00
* @ returns { MarkdownNode [ ] }
* /
function childrenWithoutProperties ( spec ) {
2022-04-13 16:13:30 -07:00
return ( spec . children || [ ] ) . filter ( c => {
2024-05-20 10:30:32 -07:00
const isProperty = c . type === 'li' && c . liType === 'bullet' && ( c . text . startsWith ( 'langs:' ) || c . text . startsWith ( 'since:' ) || c . text . startsWith ( 'deprecated:' ) || c . text . startsWith ( 'discouraged:' ) || c . text === 'hidden' ) ;
2022-04-13 16:13:30 -07:00
return ! isProperty ;
} ) ;
2021-01-07 16:12:25 -08:00
}
2021-02-11 23:43:59 -08:00
/ * *
2022-11-23 08:40:47 -08:00
* @ param { docs . Member } existingMember
* @ param { docs . Member } member
2021-02-11 23:43:59 -08:00
* @ returns { boolean }
* /
function isTypeOverride ( existingMember , member ) {
2022-09-27 08:27:23 -08:00
if ( ! existingMember . langs . only || ! member . langs . only )
2021-02-11 23:43:59 -08:00
return true ;
2022-09-27 08:27:23 -08:00
const existingOnly = existingMember . langs . only ;
if ( member . langs . only . every ( l => existingOnly . includes ( l ) ) ) {
2021-02-11 23:43:59 -08:00
return true ;
2022-09-27 08:27:23 -08:00
} else if ( member . langs . only . some ( l => existingOnly . includes ( l ) ) ) {
2021-02-11 23:43:59 -08:00
throw new Error ( ` Ambiguous language override for: ${ member . name } ` ) ;
}
return false ;
}
2022-03-02 12:43:16 -08:00
/ * *
* @ param { MarkdownNode [ ] = } params
* /
function checkNoDuplicateParamEntries ( params ) {
if ( ! params )
return ;
const entries = new Set ( ) ;
for ( const node of params ) {
if ( entries . has ( node . text ) )
throw new Error ( 'Duplicate param entry, for language-specific params use prefix (e.g. js-...): ' + node . text ) ;
entries . add ( node . text ) ;
}
}
2021-01-07 15:00:04 -08:00
module . exports = { parseApi } ;