import { ClassSymbol , ElementSymbol , ImportSymbol , MappedStates , cssParse , StateParsedValue , StylableMeta , StylableProcessor , valueMapping , VarSymbol , } from '@stylable/core' ; import { getCssDocsForSymbol } from './cssdocs' ; import { MinimalPath , SchemaStates , StateDict , stylableCssVar , StylableModuleSchema , StylableSymbolSchema , stylableVar , } from './types' ; export function extractSchema ( css , filePath , root , path , resolveNamespace ? ) { const processor = new StylableProcessor ( undefined , resolveNamespace ) ; const meta = processor . process ( cssParse ( css , { from : filePath } ) ) ; return generateSchema ( meta , filePath , root , path ) ; } export function generateSchema ( meta , filePath , basePath , path ) { const schema = { $id : `/${ path . relative ( basePath , filePath ) . replace ( /\\/g , '/' ) }` , $ref : 'stylable/module' , namespace : meta . namespace , properties : { } , } ; for ( const [ entry , symbol ] of Object . entries ( meta . getAllSymbols ( ) ) ) { if ( typeof schema . properties [ entry ] === 'boolean' ) { continue ; } else { if ( symbol . _kind === 'class' || symbol . _kind === 'element' ) { schema . properties [ entry ] = { } ; const schemaEntry = schema . properties [ entry ] as StylableSymbolSchema ; const { [ valueMapping . states ] : states , [ valueMapping . extends ] : extended } = symbol ; if ( symbol . alias && symbol . alias . import ) { addModuleDependency ( schema , filePath , symbol . alias . import . from , basePath , path ) ; schemaEntry . $ref = getImportedRef ( filePath , symbol . alias , basePath , path ) ; } else { schemaEntry . $ref = `stylable/${ symbol . _kind }` ; } if ( states ) { schemaEntry . states = getStatesForSymbol ( states ) ; } if ( extended ) { schemaEntry . extends = extended . _kind === 'import' && extended . import ? { $ref : getImportedRef ( filePath , extended , basePath , path ) } : { $ref : extended . name } ; } generateCssDocs ( meta , symbol , schemaEntry ) ; } else if ( symbol . _kind === 'var' ) { schema . properties [ entry ] = { $ref : stylableVar , } ; generateCssDocs ( meta , symbol , schema . properties [ entry ] as StylableSymbolSchema ) ; } else if ( symbol . _kind === 'cssVar' ) { schema . properties [ entry ] = { $ref : stylableCssVar , } ; } } } return schema ; } function generateCssDocs ( meta , symbol , schemaEntry ) { const cssDoc = getCssDocsForSymbol ( meta , symbol ) ; if ( cssDoc ) { if ( cssDoc . description ) { schemaEntry . description = cssDoc . description ; } if ( Object . keys ( cssDoc . tags ) . length ) { schemaEntry . docTags = cssDoc . tags ; } } } function addModuleDependency ( schema , filePath , importPath , basePath , path ) { if ( ! schema . moduleDependencies ) { schema . moduleDependencies = [ ] ; } const importedPath = normalizeImportPath ( filePath , importPath , basePath , path ) ; if ( ! schema . moduleDependencies . includes ( importedPath ) ) { schema . moduleDependencies . push ( importedPath ) ; } } function getStatesForSymbol ( states ) { const res = { } ; for ( const stateName of Object . keys ( states ) ) { const stateDef = states [ stateName ] ; if ( stateDef === null ) { res [ stateName ] = { type : 'boolean' } ; } else if ( typeof stateDef === 'object' ) { res [ stateName ] = convertMappedStateToSchema ( stateDef ) ; } else if ( typeof stateDef === 'string' ) { res [ stateName ] = { type : 'mapped' } ; } } return res ; } function convertMappedStateToSchema ( state ) { const stateSchema = { type : state . type } ; if ( state . defaultValue ) { stateSchema . default = state . defaultValue ; } if ( state . arguments . length ) { stateSchema . enum = [ ] ; stateSchema . type = 'string' ; for ( const arg of state . arguments ) { if ( typeof arg === 'string' ) { stateSchema . enum . push ( arg ) ; } } } return stateSchema ; } function getImportedRef ( fileName , importSymbol , basePath , path ) { const suffix = importSymbol . type === 'default' ? 'root' : `${ importSymbol . name }` ; return `${ normalizeImportPath ( fileName , importSymbol . import . request , basePath , path ) }#${ suffix }` ; } function normalizeImportPath ( fileName , importString , basePath , path ) { if ( importString . startsWith ( '.' ) ) { return ( '/' + path . join ( path . dirname ( path . relative ( basePath , fileName ) ) , importString ) . replace ( /\\/g , '/' ) ) ; } else if ( path . isAbsolute ( importString ) ) { return '/' + path . relative ( basePath , importString ) . replace ( /\\/g , '/' ) ; } else { return importString . replace ( /\\/g , '/' ) ; } }