license import * as babel from '@babel/types' ; import * as jsdoc from 'doctrine' ; import * as fsExtra from 'fs-extra' ; import * as minimatch from 'minimatch' ; import * as path from 'path' ; import * as analyzer from 'polymer-analyzer' ; import { Function as AnalyzerFunction } from 'polymer-analyzer/lib/javascript/function' ; import Uri from 'vscode-uri' ; import { closureParamToTypeScript , closureTypeToTypeScript } from './closure-types' ; import { isEsModuleDocument , resolveImportExportFeature } from './es-modules' ; import * as ts from './ts-ast' ; /**
 * Configuration for declaration generation.
 */ export interface Config { /**
     * Skip source files whose paths match any of these glob patterns. If
     * undefined, defaults to excluding "index.html" and directories ending in
     * "test" or "demo".
     */ excludeFiles ? : string [ ] ; /**
     * The same as `excludeFiles`, for backwards compatibility. Will be removed in
     * next major version.
     */ exclude ? : string [ ] ; /**
     * Do not emit any declarations for features that have any of these
     * identifiers.
     */ excludeIdentifiers ? : string [ ] ; /**
     * Remove any triple-slash references to these files, specified as paths
     * relative to the analysis root directory.
     */ removeReferences ? : string [ ] ; /**
     * Additional files to insert as triple-slash reference statements. Given the
     * map `a: b[]`, a will get an additional reference statement for each file
     * path in b. All paths are relative to the analysis root directory.
     */ addReferences ? : { [ filepath ] : string [ ] ; } ; /**
     * Whenever a type with a name in this map is encountered, replace it with
     * the given name. Note this only applies to named types found in places like
     * function/method parameters and return types. It does not currently rename
     * e.g. entire generated classes.
     */ renameTypes ? : { [ name ] : string ; } ; /**
     * A map from an ES module path (relative to the analysis root directory) to
     * an array of identifiers exported by that module. If any of those
     * identifiers are encountered in a generated typings file, an import for that
     * identifier from the specified module will be inserted into the typings
     * file.
     */ autoImport ? : { [ modulePath ] : string [ ] ; } ; /**
     * If true, outputs declarations in 'goog:' modules instead of using
     * simple ES modules. This is a temporary hack to account for how modules are
     * resolved for TypeScript inside google3. This is probably not at all useful
     * for anyone but the Polymer team.
     */ googModules ? : boolean ; /**
     * If true, does not log warnings detected when analyzing code,
     * only diagnostics of Error severity.
     */ hideWarnings ? : boolean ; } const defaultExclude = [ 'index.html' , 'test/**' , 'demo/**' , ] ; /**
 * Analyze all files in the given directory using Polymer Analyzer, and return
 * TypeScript declaration document strings in a map keyed by relative path.
 */ export async function generateDeclarations ( rootDir , config ) { const isBowerProject = await fsExtra . pathExists ( path . join ( rootDir , 'bower_components' ) ) === true ; const a = new analyzer . Analyzer ( { urlLoader : new analyzer . FsUrlLoader ( rootDir ) , urlResolver : new analyzer . PackageUrlResolver ( { packageDir : rootDir , componentDir : isBowerProject ? 'bower_components/' : 'node_modules/' , } ) , moduleResolution : isBowerProject ? undefined : 'node' , } ) ; const analysis = await a . analyzePackage ( ) ; const outFiles = new Map < string , string > ( ) ; for ( const tsDoc of await analyzerToAst ( analysis , config , rootDir ) ) { outFiles . set ( tsDoc . path , tsDoc . serialize ( ) ) ; } return outFiles ; } /**
 * Make TypeScript declaration documents from the given Polymer Analyzer
 * result.
 */ async function analyzerToAst ( analysis , config , rootDir ) { const excludeFiles = ( config . excludeFiles || config . exclude || defaultExclude ) . map ( ( p ) => new minimatch . Minimatch ( p ) ) ; const addReferences = config . addReferences || { } ; const removeReferencesResolved = new Set ( ( config . removeReferences || [ ] ) . map ( ( r ) => path . resolve ( rootDir , r ) ) ) ; const renameTypes = new Map ( Object . entries ( config . renameTypes || { } ) ) ; const autoImportMap = new Map < string , string > ( ) ; if ( config . autoImport !== undefined ) { for ( const importPath in config . autoImport ) { for ( const identifier of config . autoImport [ importPath ] ) { autoImportMap . set ( identifier , importPath ) ; } } } const analyzerDocs = [ ... analysis . getFeatures ( { kind : 'html-document' } ) , ... analysis . getFeatures ( { kind : 'js-document' } ) , ] ; const declarationDocs = new Map < string , analyzer . Document [ ] > ( ) ; for ( const jsDoc of analyzerDocs ) { if ( jsDoc . isInline ) { continue ; } const sourcePath = analyzerUrlToRelativePath ( jsDoc . url , rootDir ) ; if ( sourcePath === undefined ) { console . warn ( `Skipping source document without local file URL: ${ jsDoc . url }` ) ; continue ; } if ( excludeFiles . some ( ( r ) => r . match ( sourcePath ) ) ) { continue ; } const filename = makeDeclarationsFilename ( sourcePath ) ; let docs = declarationDocs . get ( filename ) ; if ( ! docs ) { docs = [ ] ; declarationDocs . set ( filename , docs ) ; } docs . push ( jsDoc ) ; } const tsDocs = [ ] ; const warnings = [ ... analysis . getWarnings ( ) ] ; for ( const [ declarationsFilename , analyzerDocs ] of declarationDocs ) { const tsDoc = new ts . Document ( { path : declarationsFilename , header : makeHeader ( analyzerDocs . map ( ( d ) => analyzerUrlToRelativePath ( d . url , rootDir ) ) . filter ( ( url ) : url is string => url !== undefined ) ) , tsLintDisables : [ { ruleName : 'variable-name' , why : `Describing an API that's defined elsewhere.` , } ] , } ) ; for ( const analyzerDoc of analyzerDocs ) { if ( isEsModuleDocument ( analyzerDoc ) ) { tsDoc . isEsModule = true ; } } for ( const analyzerDoc of analyzerDocs ) { const generator = new TypeGenerator ( tsDoc , analysis , analyzerDoc , rootDir , config . excludeIdentifiers || [ ] ) ; generator . handleDocument ( ) ; warnings . push ( ... generator . warnings ) ; } for ( const ref of tsDoc . referencePaths ) { const resolvedRef = path . resolve ( rootDir , path . dirname ( tsDoc . path ) , ref ) ; if ( removeReferencesResolved . has ( resolvedRef ) ) { tsDoc . referencePaths . delete ( ref ) ; } } for ( const ref of addReferences [ tsDoc . path ] || [ ] ) { tsDoc . referencePaths . add ( path . relative ( path . dirname ( tsDoc . path ) , ref ) ) ; } for ( const node of tsDoc . traverse ( ) ) { if ( node . kind === 'name' ) { const renamed = renameTypes . get ( node . name ) ; if ( renamed !== undefined ) { node . name = renamed ; } } } addAutoImports ( tsDoc , autoImportMap ) ; tsDoc . simplify ( ) ; tsDocs . push ( tsDoc ) ; } const filteredWarnings = warnings . filter ( ( warning ) => { if ( config . hideWarnings && warning . severity !== analyzer . Severity . ERROR ) { return false ; } const sourcePath = analyzerUrlToRelativePath ( warning . sourceRange . file , rootDir ) ; return sourcePath !== undefined && ! excludeFiles . some ( ( pattern ) => pattern . match ( sourcePath ) ) ; } ) ; const warningPrinter = new analyzer . WarningPrinter ( process . stderr , { maxCodeLines : 1 } ) ; await warningPrinter . printWarnings ( filteredWarnings ) ; if ( filteredWarnings . some ( ( warning ) => warning . severity === analyzer . Severity . ERROR ) ) { throw new Error ( 'Encountered error generating types.' ) ; } if ( config . googModules ) { return tsDocs . map ( ( d ) => transformToGoogStyle ( d , rootDir ) ) ; } return tsDocs ; } /**
 * Insert imports into the typings for any referenced identifiers listed in the
 * autoImport configuration, unless they are already imported.
 */ function addAutoImports ( tsDoc , autoImport ) { const alreadyImported = getImportedIdentifiers ( tsDoc ) ; for ( const node of tsDoc . traverse ( ) ) { if ( node . kind === 'name' ) { let importSpecifier = autoImport . get ( node . name ) ; if ( importSpecifier === undefined ) { continue ; } if ( alreadyImported . has ( node . name ) ) { continue ; } if ( importSpecifier . startsWith ( '.' ) ) { if ( makeDeclarationsFilename ( importSpecifier ) === tsDoc . path ) { continue ; } importSpecifier = path . relative ( path . dirname ( tsDoc . path ) , importSpecifier ) ; if ( ! importSpecifier . startsWith ( '.' ) ) { importSpecifier = './' + importSpecifier ; } } tsDoc . members . push ( new ts . Import ( { identifiers : [ { identifier : node . name } ] , fromModuleSpecifier : importSpecifier , } ) ) ; alreadyImported . add ( node . name ) ; } } } function getPackageName ( rootDir ) { let packageInfo ; try { packageInfo = JSON . parse ( fsExtra . readFileSync ( path . join ( rootDir , 'package.json' ) , 'utf-8' ) ) ; } catch { return undefined ; } return packageInfo . name ; } function googModuleForNameBasedImportSpecifier ( spec ) { const name = spec . replace ( /(\.d\.ts|\.js)$/ , '' ) . replace ( /[-\.](\w)/g , ( _ , s ) => s . toUpperCase ( ) ) . replace ( /^@/g , '' ) . replace ( /\//g , '.' ) ; return `goog:${ name }` ; } function transformToGoogStyle ( tsDoc , rootDir ) { const packageName = getPackageName ( rootDir ) ; if ( ! tsDoc . isEsModule || ! packageName ) { return tsDoc ; } for ( const child of tsDoc . traverse ( ) ) { if ( child . kind === 'import' || child . kind === 'export' ) { if ( ! child . fromModuleSpecifier ) { continue ; } let spec = child . fromModuleSpecifier ; if ( spec . startsWith ( '.' ) ) { spec = path . join ( packageName , path . relative ( rootDir , path . join ( rootDir , path . dirname ( tsDoc . path ) , spec ) ) . replace ( /^\.\// , '' ) ) ; } const elementName = spec . split ( '/' ) [ 1 ] ; let trailingComment = undefined ; if ( elementName && ! /\./ . test ( elementName ) ) { trailingComment = ` // from //third_party/javascript/polymer/v2/${ elementName }` ; } const googSpecifier = googModuleForNameBasedImportSpecifier ( spec ) ; if ( googSpecifier !== undefined ) { child . fromModuleSpecifier = googSpecifier ; child . trailingComment = trailingComment ; } } } let googModuleName = googModuleForNameBasedImportSpecifier ( path . join ( packageName , tsDoc . path ) ) ; if ( googModuleName === undefined ) { googModuleName = tsDoc . path ; } return new ts . Document ( { path : tsDoc . path , header : tsDoc . header , referencePaths : tsDoc . referencePaths , tsLintDisables : tsDoc . tsLintDisables , isEsModule : false , members : [ new ts . Namespace ( { name : googModuleName , members : tsDoc . members , style : 'module' } ) ] } ) ; } /**
 * Return all local identifiers imported by the given typings.
 */ function getImportedIdentifiers ( tsDoc ) { const identifiers = new Set < string > ( ) ; for ( const member of tsDoc . members ) { if ( member . kind === 'import' ) { for ( const { identifier , alias } of member . identifiers ) { if ( identifier !== ts . AllIdentifiers ) { identifiers . add ( alias || identifier ) ; } } } } return identifiers ; } /**
 * Analyzer always returns fully specified URLs with a protocol and an absolute
 * path (e.g. "file:/foo/bar"). Return just the file path, relative to our
 * project root.
 */ function analyzerUrlToRelativePath ( analyzerUrl , rootDir ) { const parsed = Uri . parse ( analyzerUrl ) ; if ( parsed . scheme !== 'file' || parsed . authority || ! parsed . fsPath ) { return undefined ; } return path . relative ( rootDir , parsed . fsPath ) ; } /**
 * Create a TypeScript declarations filename for the given source document URL.
 * Simply replaces the file extension with `d.ts`.
 */ function makeDeclarationsFilename ( sourceUrl ) { const parsed = path . parse ( sourceUrl ) ; return path . join ( parsed . dir , parsed . name ) + '.d.ts' ; } /**
 * Generate the header comment to show at the top of a declarations document.
 */ function makeHeader ( sourceUrls ) { return `DO NOT EDIT

This file was automatically generated by
  https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations

To modify these typings, edit the source file(s):
${ sourceUrls . map ( ( url ) => '  ' + url ) . join ( '\n' ) }` ; } class TypeGenerator { public warnings : analyzer . Warning [ ] = [ ] ; private excludeIdentifiers : Set < String > ; /**
     * Identifiers in this set will always be considered resolvable, e.g.
     * for when determining what identifiers should be exported.
     */ private forceResolvable = new Set < string > ( ) ; constructor ( private root , private analysis , private analyzerDoc , private rootDir , excludeIdentifiers ) { this . excludeIdentifiers = new Set ( excludeIdentifiers ) ; } private warn ( feature , message ) { this . warnings . push ( new analyzer . Warning ( { message , sourceRange : feature . sourceRange ! , severity : analyzer . Severity . WARNING , code : 'GEN_TYPESCRIPT_DECLARATIONS_WARNING' , parsedDocument : this . analyzerDoc . parsedDocument , } ) ) ; } /**
     * Extend the given TypeScript declarations document with all of the relevant
     * items in the given Polymer Analyzer document.
     */ handleDocument ( ) { for ( const feature of this . analyzerDoc . getFeatures ( ) ) { if ( [ ... feature . identifiers ] . some ( ( id ) => this . excludeIdentifiers . has ( id ) ) ) { continue ; } if ( isPrivate ( feature ) ) { continue ; } if ( feature . kinds . has ( 'element' ) ) { this . handleElement ( feature as analyzer . Element ) ; } else if ( feature . kinds . has ( 'behavior' ) ) { this . handleBehavior ( feature as analyzer . PolymerBehavior ) ; } else if ( feature . kinds . has ( 'element-mixin' ) ) { this . handleMixin ( feature as analyzer . ElementMixin ) ; } else if ( feature . kinds . has ( 'class' ) ) { this . handleClass ( feature as analyzer . Class ) ; } else if ( feature . kinds . has ( 'function' ) ) { this . handleFunction ( feature as AnalyzerFunction ) ; } else if ( feature . kinds . has ( 'namespace' ) ) { this . handleNamespace ( feature as analyzer . Namespace ) ; } else if ( feature . kinds . has ( 'html-import' ) ) { if ( feature . sourceRange && feature . sourceRange . file === this . analyzerDoc . url ) { this . handleHtmlImport ( feature as analyzer . Import ) ; } } else if ( feature . kinds . has ( 'js-import' ) ) { this . handleJsImport ( feature as analyzer . JavascriptImport ) ; } else if ( feature . kinds . has ( 'export' ) ) { this . handleJsExport ( feature as analyzer . Export ) ; } } } /**
     * Add the given Element to the given TypeScript declarations document.
     */ private handleElement ( feature ) { let constructable ; let fullName ; let shortName ; let parent ; if ( feature . className ) { constructable = true ; let namespacePath ; [ namespacePath , shortName ] = splitReference ( feature . className ) ; fullName = feature . className ; parent = findOrCreateNamespace ( this . root , namespacePath ) ; } else if ( feature . tagName ) { constructable = false ; shortName = kebabToCamel ( feature . tagName ) + 'Element' ; fullName = shortName ; parent = this . root ; } else { this . warn ( feature , `Could not find element name.` ) ; return ; } const legacyPolymerInterfaces = [ ] ; if ( isPolymerElement ( feature ) ) { legacyPolymerInterfaces . push ( ... feature . behaviorAssignments . map ( ( behavior ) => behavior . identifier ) ) ; if ( feature . isLegacyFactoryCall ) { if ( this . root . isEsModule ) { legacyPolymerInterfaces . push ( 'LegacyElementMixin' ) ; if ( ! getImportedIdentifiers ( this . root ) . has ( 'LegacyElementMixin' ) ) { this . root . members . push ( new ts . Import ( { identifiers : [ { identifier : 'LegacyElementMixin' } ] , fromModuleSpecifier : '@polymer/polymer/lib/legacy/legacy-element-mixin.js' , } ) ) ; } } else { legacyPolymerInterfaces . push ( 'Polymer.LegacyElementMixin' ) ; } legacyPolymerInterfaces . push ( 'HTMLElement' ) ; } } if ( constructable ) { this . handleClass ( feature ) ; if ( legacyPolymerInterfaces . length > 0 ) { parent . members . push ( new ts . Interface ( { name : shortName , extends : legacyPolymerInterfaces , } ) ) ; } } else { parent . members . push ( new ts . Interface ( { name : shortName , description : feature . description || feature . summary , properties : this . handleProperties ( feature . properties . values ( ) ) , methods : this . handleMethods ( feature . methods . values ( ) ) , extends : [ ... feature . mixins . map ( ( mixin ) => mixin . identifier ) , ... legacyPolymerInterfaces , ] , } ) ) ; if ( isPolymerElement ( feature ) && feature . isLegacyFactoryCall && this . root . isEsModule ) { this . root . members . push ( new ts . Export ( { identifiers : [ { identifier : shortName } ] } ) ) ; } } if ( feature . tagName ) { const elementMap = findOrCreateInterface ( this . root . isEsModule ? findOrCreateGlobalNamespace ( this . root ) : this . root , 'HTMLElementTagNameMap' ) ; elementMap . properties . push ( new ts . Property ( { name : feature . tagName , type : new ts . NameType ( fullName ) , } ) ) ; } } /**
     * Add the given Polymer Behavior to the given TypeScript declarations
     * document.
     */ private handleBehavior ( feature ) { if ( ! feature . className ) { this . warn ( feature , `Could not find a name for behavior.` ) ; return ; } const [ namespacePath , className ] = splitReference ( feature . className ) ; const ns = findOrCreateNamespace ( this . root , namespacePath ) ; ns . members . push ( new ts . Interface ( { name : className , description : feature . description || feature . summary , extends : feature . behaviorAssignments . map ( ( b ) => b . identifier ) , properties : this . handleProperties ( feature . properties . values ( ) ) , methods : this . handleMethods ( feature . methods . values ( ) ) , } ) ) ; ns . members . push ( new ts . ConstValue ( { name : className , type : new ts . NameType ( 'object' ) , } ) ) ; } /**
     * Add the given Mixin to the given TypeScript declarations document.
     */ private handleMixin ( feature ) { const [ namespacePath , mixinName ] = splitReference ( feature . name ) ; const parentNamespace = findOrCreateNamespace ( this . root , namespacePath ) ; const transitiveMixins = [ ... this . transitiveMixins ( feature ) ] ; const constructorName = mixinName + 'Constructor' ; parentNamespace . members . push ( new ts . Function ( { name : mixinName , description : feature . description , templateTypes : [ 'T extends new (...args: any[]) => {}' ] , params : [ new ts . ParamType ( { name : 'base' , type : new ts . NameType ( 'T' ) } ) , ] , returns : new ts . IntersectionType ( [ new ts . NameType ( 'T' ) , new ts . NameType ( constructorName ) , ... transitiveMixins . map ( ( mixin ) => new ts . NameType ( mixin . name + 'Constructor' ) ) ] ) , } ) ) ; if ( this . root . isEsModule ) { for ( const mixin of transitiveMixins ) { if ( mixin . sourceRange === undefined ) { continue ; } const rootRelative = analyzerUrlToRelativePath ( mixin . sourceRange . file , this . rootDir ) ; if ( rootRelative === undefined ) { continue ; } const fileRelative = path . relative ( path . dirname ( this . root . path ) , rootRelative ) ; const fromModuleSpecifier = fileRelative . startsWith ( '.' ) ? fileRelative : './' + fileRelative ; const identifiers = [ { identifier : mixin . name + 'Constructor' } ] ; if ( ! getImportedIdentifiers ( this . root ) . has ( mixin . name ) ) { identifiers . push ( { identifier : mixin . name } ) ; } this . root . members . push ( new ts . Import ( { identifiers , fromModuleSpecifier , } ) ) ; } } parentNamespace . members . push ( new ts . Interface ( { name : constructorName , methods : [ new ts . Method ( { name : 'new' , params : [ new ts . ParamType ( { name : 'args' , type : new ts . ArrayType ( ts . anyType ) , rest : true , } ) , ] , returns : new ts . NameType ( mixinName ) , } ) , ... this . handleMethods ( feature . staticMethods . values ( ) ) , ] , } ) ) ; if ( this . root . isEsModule ) { this . root . members . push ( new ts . Export ( { identifiers : [ { identifier : constructorName } ] } ) ) ; } parentNamespace . members . push ( new ts . Interface ( { name : mixinName , properties : this . handleProperties ( feature . properties . values ( ) ) , methods : this . handleMethods ( feature . methods . values ( ) ) , extends : transitiveMixins . map ( ( mixin ) => mixin . name ) , } ) ) ; } /**
     * Mixins can automatically apply other mixins, indicated by the @appliesMixin
     * annotation. However, since those mixins may themselves apply other mixins,
     * to know the full set of them we need to traverse down the tree.
     */ private transitiveMixins ( parentMixin , result ? ) { if ( result === undefined ) { result = new Set ( ) ; } for ( const childRef of parentMixin . mixins ) { const childMixinSet = this . analysis . getFeatures ( { id : childRef . identifier , kind : 'element-mixin' } ) ; if ( childMixinSet . size !== 1 ) { this . warn ( parentMixin , `Found ${ childMixinSet . size } features for mixin ` + `${ childRef . identifier }, expected 1.` ) ; continue ; } const childMixin = childMixinSet . values ( ) . next ( ) . value ; result . add ( childMixin ) ; this . transitiveMixins ( childMixin , result ) ; } return result ; } /**
     * Add the given Class to the given TypeScript declarations document.
     */ private handleClass ( feature ) { if ( ! feature . className ) { this . warn ( feature , `Could not find a name for class.` ) ; return ; } const [ namespacePath , name ] = splitReference ( feature . className ) ; const m = new ts . Class ( { name } ) ; m . description = feature . description ; m . properties = this . handleProperties ( feature . properties . values ( ) ) ; m . methods = [ ... this . handleMethods ( feature . staticMethods . values ( ) , { isStatic : true } ) , ... this . handleMethods ( feature . methods . values ( ) ) ] ; m . constructorMethod = this . handleConstructorMethod ( feature . constructorMethod ) ; if ( feature . superClass !== undefined ) { m . extends = feature . superClass . identifier ; } m . mixins = feature . mixins . map ( ( mixin ) => mixin . identifier ) ; findOrCreateNamespace ( this . root , namespacePath ) . members . push ( m ) ; } /**
     * Add the given Function to the given TypeScript declarations document.
     */ private handleFunction ( feature ) { const [ namespacePath , name ] = splitReference ( feature . name ) ; const f = new ts . Function ( { name , description : feature . description || feature . summary , templateTypes : feature . templateTypes , returns : closureTypeToTypeScript ( feature . return && feature . return . type , feature . templateTypes ) , returnsDescription : feature . return && feature . return . desc } ) ; for ( const param of feature . params || [ ] ) { f . params . push ( closureParamToTypeScript ( param . name , param . type , feature . templateTypes ) ) ; } findOrCreateNamespace ( this . root , namespacePath ) . members . push ( f ) ; } /**
     * Convert the given Analyzer properties to their TypeScript declaration
     * equivalent.
     */ private handleProperties ( analyzerProperties ) { const tsProperties = < ts . Property [ ] > [ ] ; for ( const property of analyzerProperties ) { if ( property . inheritedFrom || property . privacy === 'private' || this . excludeIdentifiers . has ( property . name ) ) { continue ; } const p = new ts . Property ( { name : property . name , type : closureTypeToTypeScript ( property . type ) , readOnly : property . readOnly , } ) ; p . description = property . description || '' ; tsProperties . push ( p ) ; } return tsProperties ; } /**
     * Convert the given Analyzer methods to their TypeScript declaration
     * equivalent.
     */ private handleMethods ( analyzerMethods , opts ? ) { const tsMethods = < ts . Method [ ] > [ ] ; for ( const method of analyzerMethods ) { if ( method . inheritedFrom || method . privacy === 'private' || this . excludeIdentifiers . has ( method . name ) ) { continue ; } tsMethods . push ( this . handleMethod ( method , opts ) ) ; } return tsMethods ; } /**
     * Convert the given Analyzer method to the equivalent TypeScript declaration
     */ private handleMethod ( method , opts ? ) { const m = new ts . Method ( { name : method . name , returns : closureTypeToTypeScript ( method . return && method . return . type ) , returnsDescription : method . return && method . return . desc , isStatic : opts && opts . isStatic , ignoreTypeCheck : this . documentationHasSuppressTypeCheck ( method . jsdoc ) } ) ; m . description = method . description || '' ; let requiredAhead = false ; for ( const param of reverseIter ( method . params || [ ] ) ) { const tsParam = closureParamToTypeScript ( param . name , param . type ) ; tsParam . description = param . description || '' ; if ( param . defaultValue !== undefined ) { if ( ! requiredAhead ) { tsParam . optional = true ; } else { tsParam . type = new ts . UnionType ( [ tsParam . type , ts . undefinedType ] ) ; } } else if ( ! tsParam . optional ) { requiredAhead = true ; } tsParam . rest = tsParam . rest || ! ! param . rest ; if ( tsParam . rest && tsParam . type . kind !== 'array' ) { tsParam . type = new ts . ArrayType ( tsParam . type ) ; } m . params . unshift ( tsParam ) ; } return m ; } private documentationHasSuppressTypeCheck ( annotation ) { if ( ! annotation ) { return false ; } const annotationValue = annotation . tags . find ( ( e ) => e . title === 'suppress' ) ; return annotationValue && annotationValue . description === '{checkTypes}' || false ; } private handleConstructorMethod ( method ? ) { if ( ! method ) { return ; } const m = this . handleMethod ( method ) ; m . returns = undefined ; return m ; } /**
     * Add the given namespace to the given TypeScript declarations document.
     */ private handleNamespace ( feature ) { const ns = findOrCreateNamespace ( this . root , feature . name . split ( '.' ) ) ; if ( ns . kind === 'namespace' ) { ns . description = feature . description || feature . summary || '' ; } } /**
     * Add a JavaScript import to the TypeScript declarations.
     */ private handleJsImport ( feature ) { const node = feature . astNode . node ; if ( babel . isImportDeclaration ( node ) ) { const identifiers = [ ] ; for ( const specifier of node . specifiers ) { if ( babel . isImportSpecifier ( specifier ) ) { if ( this . isResolvable ( specifier . imported . name , feature ) ) { identifiers . push ( { identifier : specifier . imported . name , alias : specifier . local . name , } ) ; } } else if ( babel . isImportDefaultSpecifier ( specifier ) ) { if ( this . isResolvable ( 'default' , feature ) ) { identifiers . push ( { identifier : 'default' , alias : specifier . local . name , } ) ; } } else if ( babel . isImportNamespaceSpecifier ( specifier ) ) { identifiers . push ( { identifier : ts . AllIdentifiers , alias : specifier . local . name , } ) ; this . forceResolvable . add ( specifier . local . name ) ; } } if ( identifiers . length > 0 ) { this . root . members . push ( new ts . Import ( { identifiers : identifiers , fromModuleSpecifier : node . source && node . source . value , } ) ) ; } } else if ( ! babel . isExportNamedDeclaration ( node ) && ! babel . isExportAllDeclaration ( node ) ) { this . warn ( feature , `Import with AST type ${ node . type } not supported.` ) ; } } /**
     * Add a JavaScript export to the TypeScript declarations.
     */ private handleJsExport ( feature ) { const node = feature . astNode . node ; if ( babel . isExportAllDeclaration ( node ) ) { this . root . members . push ( new ts . Export ( { identifiers : ts . AllIdentifiers , fromModuleSpecifier : node . source && node . source . value , } ) ) ; } else if ( babel . isExportNamedDeclaration ( node ) ) { const identifiers = [ ] ; if ( node . declaration ) { for ( const identifier of feature . identifiers ) { if ( this . isResolvable ( identifier , feature ) ) { identifiers . push ( { identifier } ) ; } } } else { for ( const specifier of node . specifiers ) { if ( this . isResolvable ( specifier . exported . name , feature ) || this . isResolvable ( specifier . local . name , feature ) ) { identifiers . push ( { identifier : specifier . local . name , alias : specifier . exported . name , } ) ; } } } if ( identifiers . length > 0 ) { this . root . members . push ( new ts . Export ( { identifiers , fromModuleSpecifier : node . source && node . source . value , } ) ) ; } } else { this . warn ( feature , `Export feature with AST node type ${ node . type } not supported.` ) ; } } /**
     * True if the given identifier can be resolved to a feature that will be
     * exported as a TypeScript type.
     */ private isResolvable ( identifier , fromFeature ) { if ( this . forceResolvable . has ( identifier ) ) { return true ; } if ( this . excludeIdentifiers . has ( identifier ) ) { return false ; } const resolved = resolveImportExportFeature ( fromFeature , identifier , this . analyzerDoc ) ; return resolved !== undefined && resolved . feature !== undefined && ! isPrivate ( resolved . feature ) && ! isBehaviorImpl ( resolved ) ; } /**
     * Add an HTML import to a TypeScript declarations file. For a given HTML
     * import, we assume there is a corresponding declarations file that was
     * generated by this same process.
     */ private handleHtmlImport ( feature ) { let sourcePath = analyzerUrlToRelativePath ( feature . url , this . rootDir ) ; if ( sourcePath === undefined ) { this . warn ( feature , `Skipping HTML import without local file URL: ${ feature . url }` ) ; return ; } sourcePath = sourcePath . replace ( /^(bower_components|node_modules)\// , '../' ) ; sourcePath = sourcePath . replace ( /^\.\.\/polymer\// , '../polymer/types/' ) ; this . root . referencePaths . add ( path . relative ( path . dirname ( this . root . path ) , makeDeclarationsFilename ( sourcePath ) ) ) ; } } /**
 * Iterate over an array backwards.
 */ function * reverseIter < T > ( arr ) { for ( let i = arr . length - 1 ; i >= 0 ; i -- ) { yield arr [ i ] ; } } /**
 * Find a document's global namespace declaration, or create one if it doesn't
 * exist.
 */ function findOrCreateGlobalNamespace ( doc ) { for ( const member of doc . members ) { if ( member . kind === 'globalNamespace' ) { return member ; } } const globalNamespace = new ts . GlobalNamespace ( ) ; doc . members . push ( globalNamespace ) ; return globalNamespace ; } /**
 * Traverse the given node to find the namespace AST node with the given path.
 * If it could not be found, add one and return it.
 */ function findOrCreateNamespace ( root , path ) { if ( ! path . length ) { return root ; } let first ; for ( const member of root . members ) { if ( member . kind === 'namespace' && member . name === path [ 0 ] ) { first = member ; break ; } } if ( ! first ) { first = new ts . Namespace ( { name : path [ 0 ] } ) ; root . members . push ( first ) ; } return findOrCreateNamespace ( first , path . slice ( 1 ) ) ; } /**
 * Traverse the given node to find the interface AST node with the given path.
 * If it could not be found, add one and return it.
 */ function findOrCreateInterface ( root , reference ) { const [ namespacePath , name ] = splitReference ( reference ) ; const namespace_ = findOrCreateNamespace ( root , namespacePath ) ; for ( const member of namespace_ . members ) { if ( member . kind === 'interface' && member . name === name ) { return member ; } } const i = new ts . Interface ( { name } ) ; namespace_ . members . push ( i ) ; return i ; } /**
 * Type guard that checks if a Polymer Analyzer feature is a PolymerElement.
 */ function isPolymerElement ( feature ) { return feature . kinds . has ( 'polymer-element' ) ; } /**
 * Return whether a reference looks like it is a FooBehaviorImpl style behavior
 * object, which we want to ignore.
 *
 * Polymer behavior libraries are often written like:
 *
 *   /** @polymerBehavior FooBehavior *\/
 *   export const FooBehaviorImpl = {};
 *
 *   /** @polymerBehavior *\/
 *   export const FooBehavior = [FooBehaviorImpl, OtherBehavior];
 *
 * In this case, Analyzer merges FooBehaviorImpl into FooBehavior and does not
 * emit a behavior feature for FooBehaviorImpl. However, there is still an
 * export feature for FooBehaviorImpl, so we exclude it here.
 */ function isBehaviorImpl ( reference ) { return reference . feature !== undefined && reference . feature . kinds . has ( 'behavior' ) && ( reference . feature as analyzer . PolymerBehavior ) . name !== reference . identifier ; } interface MaybePrivate { privacy ? : 'public' | 'private' | 'protected' ; } /**
 * Return whether the given Analyzer feature has "private" visibility.
 */ function isPrivate ( feature ) { return feature . privacy === 'private' ; } /**
 * Convert kebab-case to CamelCase.
 */ function kebabToCamel ( s ) { return s . replace ( /(^|-)(.)/g , ( _match , _p0 , p1 ) => p1 . toUpperCase ( ) ) ; } /**
 * Split a reference into an array of namespace path parts, and a name part
 * (e.g. `"Foo.Bar.Baz"` => `[ ["Foo", "Bar"], "Baz" ]`).
 */ function splitReference ( reference ) { const parts = reference . split ( '.' ) ; const namespacePath = parts . slice ( 0 , - 1 ) ; const name = parts [ parts . length - 1 ] ; return [ namespacePath , name ] ; }