├── .gitattributes ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .versionrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-typescript.cjs ├── releases │ └── yarn-3.0.0.cjs └── sdks │ ├── integrations.yml │ ├── prettier │ ├── index.js │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ ├── tsserverlibrary.js │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── config.ts ├── generator.ts ├── index.ts ├── injector.ts ├── parser.ts └── types │ ├── index.ts │ ├── nodes │ ├── baseNodes.ts │ ├── component.ts │ ├── program.ts │ └── proptype.ts │ └── props │ ├── DOMElement.ts │ ├── any.ts │ ├── array.ts │ ├── boolean.ts │ ├── element.ts │ ├── function.ts │ ├── instanceOf.ts │ ├── interface.ts │ ├── literal.ts │ ├── numeric.ts │ ├── object.ts │ ├── string.ts │ ├── undefined.ts │ └── union.ts ├── test ├── boolean-values │ ├── optional │ │ ├── input.d.ts │ │ ├── output.js │ │ └── output.json │ └── required │ │ ├── input.d.ts │ │ ├── output.js │ │ └── output.json ├── code-order │ ├── input.d.ts │ ├── input.js │ ├── options.ts │ ├── output.js │ └── output.json ├── date │ ├── input.d.ts │ ├── output.js │ └── output.json ├── destructured-forward-ref │ ├── input.tsx │ ├── output.js │ └── output.json ├── destructured-props │ ├── input.tsx │ ├── output.js │ └── output.json ├── generator │ └── html-elements │ │ ├── input.d.ts │ │ ├── output.js │ │ └── output.json ├── index.test.ts ├── injector │ ├── all-props-ignored │ │ ├── input.tsx │ │ ├── options.ts │ │ ├── output.js │ │ └── output.json │ ├── should-include-component-based │ │ ├── input.tsx │ │ ├── options.ts │ │ ├── output.js │ │ └── output.json │ ├── should-include-filename-based │ │ ├── input.tsx │ │ ├── options.ts │ │ ├── output.js │ │ └── output.json │ ├── string-props │ │ ├── input.tsx │ │ ├── output.js │ │ └── output.json │ └── whitelisted-props │ │ ├── input.tsx │ │ ├── options.ts │ │ ├── output.js │ │ └── output.json ├── mixed-literals │ ├── input.tsx │ ├── output.js │ └── output.json ├── options-test │ ├── input.tsx │ ├── options.ts │ ├── output.js │ └── output.json ├── overloaded-function-component │ ├── input.d.ts │ ├── options.ts │ ├── output.js │ └── output.json ├── react-memo │ ├── input.tsx │ ├── output.js │ └── output.json ├── reconcile-prop-types │ ├── input.d.ts │ ├── input.js │ ├── options.ts │ ├── output.js │ └── output.json ├── recursive-type │ ├── input.d.ts │ ├── output.js │ └── output.json ├── sort-unions │ ├── input.d.ts │ ├── input.js │ ├── options.ts │ ├── output.js │ └── output.json ├── type-only-import-export │ ├── external.d.ts │ ├── input.d.ts │ ├── output.js │ └── output.json ├── type-unknown │ ├── input.tsx │ ├── output.js │ └── output.json ├── types.d.ts ├── union-props │ ├── input.d.ts │ ├── input.js │ ├── output.js │ └── output.json └── unused-prop │ ├── input.tsx │ ├── output.js │ └── output.json ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Build, format, and test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | env: 10 | YARN_ENABLE_MIRROR: 'false' 11 | YARN_ENABLE_GLOBAL_CACHE: 'false' 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 14.x 19 | 20 | - uses: actions/cache@v2 21 | with: 22 | path: .yarn/cache 23 | key: yarn2-${{ hashFiles('yarn.lock') }} 24 | restore-keys: | 25 | yarn2- 26 | 27 | - run: yarn --immutable 28 | - run: yarn build 29 | - run: yarn test 30 | - run: yarn prettier --check . 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Output folder 2 | dist 3 | 4 | # Yarn 2 5 | .yarn/* 6 | !.yarn/patches 7 | !.yarn/releases 8 | !.yarn/plugins 9 | !.yarn/sdks 10 | !.yarn/versions 11 | .pnp.* 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (https://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | jspm_packages/ 50 | 51 | # TypeScript v1 declaration files 52 | typings/ 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variables file 70 | .env 71 | 72 | # next.js build output 73 | .next 74 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Yarn 2 4 | .yarn/* 5 | !.yarn/sdks 6 | .pnp.* 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "printWidth": 100, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { "type": "feat", "section": "Features" }, 4 | { "type": "fix", "section": "Bug Fixes" }, 5 | { "type": "perf", "section": "Performance Improvements" }, 6 | { "type": "build", "section": "Build System" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "arcanis.vscode-zipfs"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug test", 11 | "runtimeArgs": ["-r", "./.pnp.js", "-r", "jest/bin/jest.js"], 12 | "cwd": "${workspaceFolder}", 13 | "skipFiles": ["/**", "node_modules/**", ".pnp.js"] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 3 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js", 4 | "typescript.enablePromptUseWorkspaceTsdk": true, 5 | "search.exclude": { 6 | "**/.yarn": true, 7 | "**/.pnp.*": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-typescript.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-typescript", 5 | factory: function (require) { 6 | var plugin=(()=>{var Ft=Object.create,H=Object.defineProperty,Bt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,zt=Object.getOwnPropertyDescriptors,Gt=Object.getOwnPropertyNames,Q=Object.getOwnPropertySymbols,$t=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,De=Object.prototype.propertyIsEnumerable;var Re=(e,t,r)=>t in e?H(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,u=(e,t)=>{for(var r in t||(t={}))ne.call(t,r)&&Re(e,r,t[r]);if(Q)for(var r of Q(t))De.call(t,r)&&Re(e,r,t[r]);return e},g=(e,t)=>Bt(e,zt(t)),Lt=e=>H(e,"__esModule",{value:!0});var R=(e,t)=>{var r={};for(var s in e)ne.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(e!=null&&Q)for(var s of Q(e))t.indexOf(s)<0&&De.call(e,s)&&(r[s]=e[s]);return r};var I=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Vt=(e,t)=>{for(var r in t)H(e,r,{get:t[r],enumerable:!0})},Qt=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Gt(t))!ne.call(e,s)&&s!=="default"&&H(e,s,{get:()=>t[s],enumerable:!(r=Kt(t,s))||r.enumerable});return e},C=e=>Qt(Lt(H(e!=null?Ft($t(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var xe=I(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});function _(e){let t=[...e.caches],r=t.shift();return r===void 0?ve():{get(s,n,a={miss:()=>Promise.resolve()}){return r.get(s,n,a).catch(()=>_({caches:t}).get(s,n,a))},set(s,n){return r.set(s,n).catch(()=>_({caches:t}).set(s,n))},delete(s){return r.delete(s).catch(()=>_({caches:t}).delete(s))},clear(){return r.clear().catch(()=>_({caches:t}).clear())}}}function ve(){return{get(e,t,r={miss:()=>Promise.resolve()}){return t().then(n=>Promise.all([n,r.miss(n)])).then(([n])=>n)},set(e,t){return Promise.resolve(t)},delete(e){return Promise.resolve()},clear(){return Promise.resolve()}}}J.createFallbackableCache=_;J.createNullCache=ve});var Ee=I(($s,qe)=>{qe.exports=xe()});var Te=I(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});function Jt(e={serializable:!0}){let t={};return{get(r,s,n={miss:()=>Promise.resolve()}){let a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);let o=s(),d=n&&n.miss||(()=>Promise.resolve());return o.then(y=>d(y)).then(()=>o)},set(r,s){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete t[JSON.stringify(r)],Promise.resolve()},clear(){return t={},Promise.resolve()}}}ae.createInMemoryCache=Jt});var we=I((Vs,Me)=>{Me.exports=Te()});var Ce=I(M=>{"use strict";Object.defineProperty(M,"__esModule",{value:!0});function Xt(e,t,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers(){return e===oe.WithinHeaders?s:{}},queryParameters(){return e===oe.WithinQueryParameters?s:{}}}}function Yt(e){let t=0,r=()=>(t++,new Promise(s=>{setTimeout(()=>{s(e(r))},Math.min(100*t,1e3))}));return e(r)}function ke(e,t=(r,s)=>Promise.resolve()){return Object.assign(e,{wait(r){return ke(e.then(s=>Promise.all([t(s,r),s])).then(s=>s[1]))}})}function Zt(e){let t=e.length-1;for(t;t>0;t--){let r=Math.floor(Math.random()*(t+1)),s=e[t];e[t]=e[r],e[r]=s}return e}function er(e,t){return Object.keys(t!==void 0?t:{}).forEach(r=>{e[r]=t[r](e)}),e}function tr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}var rr="4.2.0",sr=e=>()=>e.transporter.requester.destroy(),oe={WithinQueryParameters:0,WithinHeaders:1};M.AuthMode=oe;M.addMethods=er;M.createAuth=Xt;M.createRetryablePromise=Yt;M.createWaitablePromise=ke;M.destroy=sr;M.encode=tr;M.shuffle=Zt;M.version=rr});var F=I((Js,Ue)=>{Ue.exports=Ce()});var Ne=I(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});var nr={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};ie.MethodEnum=nr});var B=I((Ys,We)=>{We.exports=Ne()});var Ze=I(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});var He=B();function ce(e,t){let r=e||{},s=r.data||{};return Object.keys(r).forEach(n=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(n)===-1&&(s[n]=r[n])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var X={Read:1,Write:2,Any:3},U={Up:1,Down:2,Timeouted:3},_e=2*60*1e3;function ue(e,t=U.Up){return g(u({},e),{status:t,lastUpdate:Date.now()})}function Fe(e){return e.status===U.Up||Date.now()-e.lastUpdate>_e}function Be(e){return e.status===U.Timeouted&&Date.now()-e.lastUpdate<=_e}function le(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||X.Any}}function ar(e,t){return Promise.all(t.map(r=>e.get(r,()=>Promise.resolve(ue(r))))).then(r=>{let s=r.filter(d=>Fe(d)),n=r.filter(d=>Be(d)),a=[...s,...n],o=a.length>0?a.map(d=>le(d)):t;return{getTimeout(d,y){return(n.length===0&&d===0?1:n.length+3+d)*y},statelessHosts:o}})}var or=({isTimedOut:e,status:t})=>!e&&~~t==0,ir=e=>{let t=e.status;return e.isTimedOut||or(e)||~~(t/100)!=2&&~~(t/100)!=4},cr=({status:e})=>~~(e/100)==2,ur=(e,t)=>ir(e)?t.onRetry(e):cr(e)?t.onSucess(e):t.onFail(e);function Qe(e,t,r,s){let n=[],a=$e(r,s),o=Le(e,s),d=r.method,y=r.method!==He.MethodEnum.Get?{}:u(u({},r.data),s.data),b=u(u(u({"x-algolia-agent":e.userAgent.value},e.queryParameters),y),s.queryParameters),f=0,p=(h,S)=>{let O=h.pop();if(O===void 0)throw Ve(de(n));let P={data:a,headers:o,method:d,url:Ge(O,r.path,b),connectTimeout:S(f,e.timeouts.connect),responseTimeout:S(f,s.timeout)},x=j=>{let T={request:P,response:j,host:O,triesLeft:h.length};return n.push(T),T},v={onSucess:j=>Ke(j),onRetry(j){let T=x(j);return j.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",pe(T)),e.hostsCache.set(O,ue(O,j.isTimedOut?U.Timeouted:U.Down))]).then(()=>p(h,S))},onFail(j){throw x(j),ze(j,de(n))}};return e.requester.send(P).then(j=>ur(j,v))};return ar(e.hostsCache,t).then(h=>p([...h.statelessHosts].reverse(),h.getTimeout))}function lr(e){let{hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,hosts:y,queryParameters:b,headers:f}=e,p={hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,headers:f,queryParameters:b,hosts:y.map(h=>le(h)),read(h,S){let O=ce(S,p.timeouts.read),P=()=>Qe(p,p.hosts.filter(j=>(j.accept&X.Read)!=0),h,O);if((O.cacheable!==void 0?O.cacheable:h.cacheable)!==!0)return P();let v={request:h,mappedRequestOptions:O,transporter:{queryParameters:p.queryParameters,headers:p.headers}};return p.responsesCache.get(v,()=>p.requestsCache.get(v,()=>p.requestsCache.set(v,P()).then(j=>Promise.all([p.requestsCache.delete(v),j]),j=>Promise.all([p.requestsCache.delete(v),Promise.reject(j)])).then(([j,T])=>T)),{miss:j=>p.responsesCache.set(v,j)})},write(h,S){return Qe(p,p.hosts.filter(O=>(O.accept&X.Write)!=0),h,ce(S,p.timeouts.write))}};return p}function dr(e){let t={value:`Algolia for JavaScript (${e})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return t.value.indexOf(s)===-1&&(t.value=`${t.value}${s}`),t}};return t}function Ke(e){try{return JSON.parse(e.content)}catch(t){throw Je(t.message,e)}}function ze({content:e,status:t},r){let s=e;try{s=JSON.parse(e).message}catch(n){}return Xe(s,t,r)}function pr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}function Ge(e,t,r){let s=Ye(r),n=`${e.protocol}://${e.url}/${t.charAt(0)==="/"?t.substr(1):t}`;return s.length&&(n+=`?${s}`),n}function Ye(e){let t=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(e).map(r=>pr("%s=%s",r,t(e[r])?JSON.stringify(e[r]):e[r])).join("&")}function $e(e,t){if(e.method===He.MethodEnum.Get||e.data===void 0&&t.data===void 0)return;let r=Array.isArray(e.data)?e.data:u(u({},e.data),t.data);return JSON.stringify(r)}function Le(e,t){let r=u(u({},e.headers),t.headers),s={};return Object.keys(r).forEach(n=>{let a=r[n];s[n.toLowerCase()]=a}),s}function de(e){return e.map(t=>pe(t))}function pe(e){let t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return g(u({},e),{request:g(u({},e.request),{headers:u(u({},e.request.headers),t)})})}function Xe(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}function Je(e,t){return{name:"DeserializationError",message:e,response:t}}function Ve(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}A.CallEnum=X;A.HostStatusEnum=U;A.createApiError=Xe;A.createDeserializationError=Je;A.createMappedRequestOptions=ce;A.createRetryError=Ve;A.createStatefulHost=ue;A.createStatelessHost=le;A.createTransporter=lr;A.createUserAgent=dr;A.deserializeFailure=ze;A.deserializeSuccess=Ke;A.isStatefulHostTimeouted=Be;A.isStatefulHostUp=Fe;A.serializeData=$e;A.serializeHeaders=Le;A.serializeQueryParameters=Ye;A.serializeUrl=Ge;A.stackFrameWithoutCredentials=pe;A.stackTraceWithoutCredentials=de});var K=I((en,et)=>{et.exports=Ze()});var tt=I(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});var N=F(),mr=K(),z=B(),hr=e=>{let t=e.region||"us",r=N.createAuth(N.AuthMode.WithinHeaders,e.appId,e.apiKey),s=mr.createTransporter(g(u({hosts:[{url:`analytics.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n=e.appId;return N.addMethods({appId:n,transporter:s},e.methods)},yr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:"2/abtests",data:t},r),gr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Delete,path:N.encode("2/abtests/%s",t)},r),fr=e=>(t,r)=>e.transporter.read({method:z.MethodEnum.Get,path:N.encode("2/abtests/%s",t)},r),br=e=>t=>e.transporter.read({method:z.MethodEnum.Get,path:"2/abtests"},t),Pr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:N.encode("2/abtests/%s/stop",t)},r);w.addABTest=yr;w.createAnalyticsClient=hr;w.deleteABTest=gr;w.getABTest=fr;w.getABTests=br;w.stopABTest=Pr});var st=I((rn,rt)=>{rt.exports=tt()});var at=I(G=>{"use strict";Object.defineProperty(G,"__esModule",{value:!0});var me=F(),jr=K(),nt=B(),Or=e=>{let t=e.region||"us",r=me.createAuth(me.AuthMode.WithinHeaders,e.appId,e.apiKey),s=jr.createTransporter(g(u({hosts:[{url:`recommendation.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)}));return me.addMethods({appId:e.appId,transporter:s},e.methods)},Ir=e=>t=>e.transporter.read({method:nt.MethodEnum.Get,path:"1/strategies/personalization"},t),Ar=e=>(t,r)=>e.transporter.write({method:nt.MethodEnum.Post,path:"1/strategies/personalization",data:t},r);G.createRecommendationClient=Or;G.getPersonalizationStrategy=Ir;G.setPersonalizationStrategy=Ar});var it=I((nn,ot)=>{ot.exports=at()});var jt=I(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});var l=F(),q=K(),m=B(),Sr=require("crypto");function Y(e){let t=r=>e.request(r).then(s=>{if(e.batch!==void 0&&e.batch(s.hits),!e.shouldStop(s))return s.cursor?t({cursor:s.cursor}):t({page:(r.page||0)+1})});return t({})}var Dr=e=>{let t=e.appId,r=l.createAuth(e.authMode!==void 0?e.authMode:l.AuthMode.WithinHeaders,t,e.apiKey),s=q.createTransporter(g(u({hosts:[{url:`${t}-dsn.algolia.net`,accept:q.CallEnum.Read},{url:`${t}.algolia.net`,accept:q.CallEnum.Write}].concat(l.shuffle([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}]))},e),{headers:u(g(u({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n={transporter:s,appId:t,addAlgoliaAgent(a,o){s.userAgent.add({segment:a,version:o})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return l.addMethods(n,e.methods)};function ct(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ut(){return{name:"ObjectNotFoundError",message:"Object not found."}}function lt(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Rr=e=>(t,r)=>{let d=r||{},{queryParameters:s}=d,n=R(d,["queryParameters"]),a=u({acl:t},s!==void 0?{queryParameters:s}:{}),o=(y,b)=>l.createRetryablePromise(f=>$(e)(y.key,b).catch(p=>{if(p.status!==404)throw p;return f()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/keys",data:a},n),o)},vr=e=>(t,r,s)=>{let n=q.createMappedRequestOptions(s);return n.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},n)},xr=e=>(t,r,s)=>e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:r}},s),Z=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"copy",destination:r}},s),n)},qr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Rules]})),Er=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Settings]})),Tr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Synonyms]})),Mr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).then(o).catch(d=>{if(d.status!==404)throw d}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/keys/%s",t)},r),s)},wr=()=>(e,t)=>{let r=q.serializeQueryParameters(t),s=Sr.createHmac("sha256",e).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},$=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/keys/%s",t)},r),kr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/logs"},t),Cr=()=>e=>{let t=Buffer.from(e,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=t.match(r);if(s===null)throw lt();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Ur=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/top"},t),Nr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/clusters/mapping/%s",t)},r),Wr=e=>t=>{let n=t||{},{retrieveMappings:r}=n,s=R(n,["retrieveMappings"]);return r===!0&&(s.getClusters=!0),e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},L=e=>(t,r={})=>{let s={transporter:e.transporter,appId:e.appId,indexName:t};return l.addMethods(s,r.methods)},Hr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/keys"},t),_r=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters"},t),Fr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/indexes"},t),Br=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping"},t),Kr=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"move",destination:r}},s),n)},zr=e=>(t,r)=>{let s=(n,a)=>Promise.all(Object.keys(n.taskID).map(o=>L(e)(o,{methods:{waitTask:D}}).waitTask(n.taskID[o],a)));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:t}},r),s)},Gr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:t}},r),$r=e=>(t,r)=>{let s=t.map(n=>g(u({},n),{params:q.serializeQueryParameters(n.params||{})}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Lr=e=>(t,r)=>Promise.all(t.map(s=>{let d=s.params,{facetName:n,facetQuery:a}=d,o=R(d,["facetName","facetQuery"]);return L(e)(s.indexName,{methods:{searchForFacetValues:dt}}).searchForFacetValues(n,a,u(u({},r),o))})),Vr=e=>(t,r)=>{let s=q.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Qr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).catch(d=>{if(d.status!==404)throw d;return o()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/keys/%s/restore",t)},r),s)},Jr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:t}},r),Xr=e=>(t,r)=>{let s=Object.assign({},r),f=r||{},{queryParameters:n}=f,a=R(f,["queryParameters"]),o=n?{queryParameters:n}:{},d=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],y=p=>Object.keys(s).filter(h=>d.indexOf(h)!==-1).every(h=>p[h]===s[h]),b=(p,h)=>l.createRetryablePromise(S=>$(e)(t,h).then(O=>y(O)?Promise.resolve():S()));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/keys/%s",t),data:o},a),b)},pt=e=>(t,r)=>{let s=(n,a)=>D(e)(n.taskID,a);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/batch",e.indexName),data:{requests:t}},r),s)},Yr=e=>t=>Y(g(u({},t),{shouldStop:r=>r.cursor===void 0,request:r=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/browse",e.indexName),data:r},t)})),Zr=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},es=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},te=e=>(t,r,s)=>{let y=s||{},{batchSize:n}=y,a=R(y,["batchSize"]),o={taskIDs:[],objectIDs:[]},d=(b=0)=>{let f=[],p;for(p=b;p({action:r,body:h})),a).then(h=>(o.objectIDs=o.objectIDs.concat(h.objectIDs),o.taskIDs.push(h.taskID),p++,d(p)))};return l.createWaitablePromise(d(),(b,f)=>Promise.all(b.taskIDs.map(p=>D(e)(p,f))))},ts=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/clear",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),rs=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ss=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ns=e=>(t,r)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/deleteByQuery",e.indexName),data:t},r),(s,n)=>D(e)(s.taskID,n)),as=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),os=e=>(t,r)=>l.createWaitablePromise(yt(e)([t],r).then(s=>({taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),yt=e=>(t,r)=>{let s=t.map(n=>({objectID:n}));return te(e)(s,k.DeleteObject,r)},is=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},cs=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},us=e=>t=>gt(e)(t).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),ls=e=>(t,r)=>{let y=r||{},{query:s,paginate:n}=y,a=R(y,["query","paginate"]),o=0,d=()=>ft(e)(s||"",g(u({},a),{page:o})).then(b=>{for(let[f,p]of Object.entries(b.hits))if(t(p))return{object:p,position:parseInt(f,10),page:o};if(o++,n===!1||o>=b.nbPages)throw ut();return d()});return d()},ds=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/%s",e.indexName,t)},r),ps=()=>(e,t)=>{for(let[r,s]of Object.entries(e.hits))if(s.objectID===t)return parseInt(r,10);return-1},ms=e=>(t,r)=>{let o=r||{},{attributesToRetrieve:s}=o,n=R(o,["attributesToRetrieve"]),a=t.map(d=>u({indexName:e.indexName,objectID:d},s?{attributesToRetrieve:s}:{}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:a}},n)},hs=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},r),gt=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),ys=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},r),bt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/task/%s",e.indexName,t.toString())},r),gs=e=>(t,r)=>l.createWaitablePromise(Pt(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),Pt=e=>(t,r)=>{let o=r||{},{createIfNotExists:s}=o,n=R(o,["createIfNotExists"]),a=s?k.PartialUpdateObject:k.PartialUpdateObjectNoCreate;return te(e)(t,a,n)},fs=e=>(t,r)=>{let O=r||{},{safe:s,autoGenerateObjectIDIfNotExist:n,batchSize:a}=O,o=R(O,["safe","autoGenerateObjectIDIfNotExist","batchSize"]),d=(P,x,v,j)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",P),data:{operation:v,destination:x}},j),(T,V)=>D(e)(T.taskID,V)),y=Math.random().toString(36).substring(7),b=`${e.indexName}_tmp_${y}`,f=he({appId:e.appId,transporter:e.transporter,indexName:b}),p=[],h=d(e.indexName,b,"copy",g(u({},o),{scope:["settings","synonyms","rules"]}));p.push(h);let S=(s?h.wait(o):h).then(()=>{let P=f(t,g(u({},o),{autoGenerateObjectIDIfNotExist:n,batchSize:a}));return p.push(P),s?P.wait(o):P}).then(()=>{let P=d(b,e.indexName,"move",o);return p.push(P),s?P.wait(o):P}).then(()=>Promise.all(p)).then(([P,x,v])=>({objectIDs:x.objectIDs,taskIDs:[P.taskID,...x.taskIDs,v.taskID]}));return l.createWaitablePromise(S,(P,x)=>Promise.all(p.map(v=>v.wait(x))))},bs=e=>(t,r)=>ye(e)(t,g(u({},r),{clearExistingRules:!0})),Ps=e=>(t,r)=>ge(e)(t,g(u({},r),{replaceExistingSynonyms:!0})),js=e=>(t,r)=>l.createWaitablePromise(he(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),he=e=>(t,r)=>{let o=r||{},{autoGenerateObjectIDIfNotExist:s}=o,n=R(o,["autoGenerateObjectIDIfNotExist"]),a=s?k.AddObject:k.UpdateObject;if(a===k.UpdateObject){for(let d of t)if(d.objectID===void 0)return l.createWaitablePromise(Promise.reject(ct()))}return te(e)(t,a,n)},Os=e=>(t,r)=>ye(e)([t],r),ye=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,clearExistingRules:n}=d,a=R(d,["forwardToReplicas","clearExistingRules"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.clearExistingRules=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},Is=e=>(t,r)=>ge(e)([t],r),ge=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,replaceExistingSynonyms:n}=d,a=R(d,["forwardToReplicas","replaceExistingSynonyms"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.replaceExistingSynonyms=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},ft=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),dt=e=>(t,r,s)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},s),mt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/search",e.indexName),data:{query:t}},r),ht=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},r),As=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/indexes/%s/settings",e.indexName),data:t},a),(d,y)=>D(e)(d.taskID,y))},D=e=>(t,r)=>l.createRetryablePromise(s=>bt(e)(t,r).then(n=>n.status!=="published"?s():void 0)),Ss={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},k={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},ee={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Ds={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Rs={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};i.ApiKeyACLEnum=Ss;i.BatchActionEnum=k;i.ScopeEnum=ee;i.StrategyEnum=Ds;i.SynonymEnum=Rs;i.addApiKey=Rr;i.assignUserID=vr;i.assignUserIDs=xr;i.batch=pt;i.browseObjects=Yr;i.browseRules=Zr;i.browseSynonyms=es;i.chunkedBatch=te;i.clearObjects=ts;i.clearRules=rs;i.clearSynonyms=ss;i.copyIndex=Z;i.copyRules=qr;i.copySettings=Er;i.copySynonyms=Tr;i.createBrowsablePromise=Y;i.createMissingObjectIDError=ct;i.createObjectNotFoundError=ut;i.createSearchClient=Dr;i.createValidUntilNotFoundError=lt;i.deleteApiKey=Mr;i.deleteBy=ns;i.deleteIndex=as;i.deleteObject=os;i.deleteObjects=yt;i.deleteRule=is;i.deleteSynonym=cs;i.exists=us;i.findObject=ls;i.generateSecuredApiKey=wr;i.getApiKey=$;i.getLogs=kr;i.getObject=ds;i.getObjectPosition=ps;i.getObjects=ms;i.getRule=hs;i.getSecuredApiKeyRemainingValidity=Cr;i.getSettings=gt;i.getSynonym=ys;i.getTask=bt;i.getTopUserIDs=Ur;i.getUserID=Nr;i.hasPendingMappings=Wr;i.initIndex=L;i.listApiKeys=Hr;i.listClusters=_r;i.listIndices=Fr;i.listUserIDs=Br;i.moveIndex=Kr;i.multipleBatch=zr;i.multipleGetObjects=Gr;i.multipleQueries=$r;i.multipleSearchForFacetValues=Lr;i.partialUpdateObject=gs;i.partialUpdateObjects=Pt;i.removeUserID=Vr;i.replaceAllObjects=fs;i.replaceAllRules=bs;i.replaceAllSynonyms=Ps;i.restoreApiKey=Qr;i.saveObject=js;i.saveObjects=he;i.saveRule=Os;i.saveRules=ye;i.saveSynonym=Is;i.saveSynonyms=ge;i.search=ft;i.searchForFacetValues=dt;i.searchRules=mt;i.searchSynonyms=ht;i.searchUserIDs=Jr;i.setSettings=As;i.updateApiKey=Xr;i.waitTask=D});var It=I((on,Ot)=>{Ot.exports=jt()});var At=I(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});function vs(){return{debug(e,t){return Promise.resolve()},info(e,t){return Promise.resolve()},error(e,t){return Promise.resolve()}}}var xs={Debug:1,Info:2,Error:3};re.LogLevelEnum=xs;re.createNullLogger=vs});var Dt=I((un,St)=>{St.exports=At()});var xt=I(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});var Rt=require("http"),vt=require("https"),qs=require("url");function Es(){let e={keepAlive:!0},t=new Rt.Agent(e),r=new vt.Agent(e);return{send(s){return new Promise(n=>{let a=qs.parse(s.url),o=a.query===null?a.pathname:`${a.pathname}?${a.query}`,d=u({agent:a.protocol==="https:"?r:t,hostname:a.hostname,path:o,method:s.method,headers:s.headers},a.port!==void 0?{port:a.port||""}:{}),y=(a.protocol==="https:"?vt:Rt).request(d,h=>{let S="";h.on("data",O=>S+=O),h.on("end",()=>{clearTimeout(f),clearTimeout(p),n({status:h.statusCode||0,content:S,isTimedOut:!1})})}),b=(h,S)=>setTimeout(()=>{y.abort(),n({status:0,content:S,isTimedOut:!0})},h*1e3),f=b(s.connectTimeout,"Connection timeout"),p;y.on("error",h=>{clearTimeout(f),clearTimeout(p),n({status:0,content:h.message,isTimedOut:!1})}),y.once("response",()=>{clearTimeout(f),p=b(s.responseTimeout,"Socket timeout")}),s.data!==void 0&&y.write(s.data),y.end()})},destroy(){return t.destroy(),r.destroy(),Promise.resolve()}}}fe.createNodeHttpRequester=Es});var Et=I((dn,qt)=>{qt.exports=xt()});var kt=I((pn,Tt)=>{"use strict";var Mt=Ee(),Ts=we(),W=st(),be=F(),Pe=it(),c=It(),Ms=Dt(),ws=Et(),ks=K();function wt(e,t,r){let s={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:ws.createNodeHttpRequester(),logger:Ms.createNullLogger(),responsesCache:Mt.createNullCache(),requestsCache:Mt.createNullCache(),hostsCache:Ts.createInMemoryCache(),userAgent:ks.createUserAgent(be.version).add({segment:"Node.js",version:process.versions.node})};return c.createSearchClient(g(u(u({},s),r),{methods:{search:c.multipleQueries,searchForFacetValues:c.multipleSearchForFacetValues,multipleBatch:c.multipleBatch,multipleGetObjects:c.multipleGetObjects,multipleQueries:c.multipleQueries,copyIndex:c.copyIndex,copySettings:c.copySettings,copyRules:c.copyRules,copySynonyms:c.copySynonyms,moveIndex:c.moveIndex,listIndices:c.listIndices,getLogs:c.getLogs,listClusters:c.listClusters,multipleSearchForFacetValues:c.multipleSearchForFacetValues,getApiKey:c.getApiKey,addApiKey:c.addApiKey,listApiKeys:c.listApiKeys,updateApiKey:c.updateApiKey,deleteApiKey:c.deleteApiKey,restoreApiKey:c.restoreApiKey,assignUserID:c.assignUserID,assignUserIDs:c.assignUserIDs,getUserID:c.getUserID,searchUserIDs:c.searchUserIDs,listUserIDs:c.listUserIDs,getTopUserIDs:c.getTopUserIDs,removeUserID:c.removeUserID,hasPendingMappings:c.hasPendingMappings,generateSecuredApiKey:c.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:c.getSecuredApiKeyRemainingValidity,destroy:be.destroy,initIndex:n=>a=>c.initIndex(n)(a,{methods:{batch:c.batch,delete:c.deleteIndex,getObject:c.getObject,getObjects:c.getObjects,saveObject:c.saveObject,saveObjects:c.saveObjects,search:c.search,searchForFacetValues:c.searchForFacetValues,waitTask:c.waitTask,setSettings:c.setSettings,getSettings:c.getSettings,partialUpdateObject:c.partialUpdateObject,partialUpdateObjects:c.partialUpdateObjects,deleteObject:c.deleteObject,deleteObjects:c.deleteObjects,deleteBy:c.deleteBy,clearObjects:c.clearObjects,browseObjects:c.browseObjects,getObjectPosition:c.getObjectPosition,findObject:c.findObject,exists:c.exists,saveSynonym:c.saveSynonym,saveSynonyms:c.saveSynonyms,getSynonym:c.getSynonym,searchSynonyms:c.searchSynonyms,browseSynonyms:c.browseSynonyms,deleteSynonym:c.deleteSynonym,clearSynonyms:c.clearSynonyms,replaceAllObjects:c.replaceAllObjects,replaceAllSynonyms:c.replaceAllSynonyms,searchRules:c.searchRules,getRule:c.getRule,deleteRule:c.deleteRule,saveRule:c.saveRule,saveRules:c.saveRules,replaceAllRules:c.replaceAllRules,browseRules:c.browseRules,clearRules:c.clearRules}}),initAnalytics:()=>n=>W.createAnalyticsClient(g(u(u({},s),n),{methods:{addABTest:W.addABTest,getABTest:W.getABTest,getABTests:W.getABTests,stopABTest:W.stopABTest,deleteABTest:W.deleteABTest}})),initRecommendation:()=>n=>Pe.createRecommendationClient(g(u(u({},s),n),{methods:{getPersonalizationStrategy:Pe.getPersonalizationStrategy,setPersonalizationStrategy:Pe.setPersonalizationStrategy}}))}}))}wt.version=be.version;Tt.exports=wt});var Ut=I((mn,je)=>{var Ct=kt();je.exports=Ct;je.exports.default=Ct});var Ws={};Vt(Ws,{default:()=>Ks});var Oe=C(require("@yarnpkg/core")),E=C(require("@yarnpkg/core")),Ie=C(require("@yarnpkg/plugin-essentials")),Ht=C(require("semver"));var se=C(require("@yarnpkg/core")),Nt=C(Ut()),Cs="e8e1bd300d860104bb8c58453ffa1eb4",Us="OFCNCOG2CU",Wt=async(e,t)=>{var a;let r=se.structUtils.stringifyIdent(e),n=Ns(t).initIndex("npm-search");try{return((a=(await n.getObject(r,{attributesToRetrieve:["types"]})).types)==null?void 0:a.ts)==="definitely-typed"}catch(o){return!1}},Ns=e=>(0,Nt.default)(Us,Cs,{requester:{async send(r){try{let s=await se.httpUtils.request(r.url,r.data||null,{configuration:e,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var _t=e=>e.scope?`${e.scope}__${e.name}`:`${e.name}`,Hs=async(e,t,r,s)=>{if(r.scope==="types")return;let{project:n}=e,{configuration:a}=n,o=a.makeResolver(),d={project:n,resolver:o,report:new E.ThrowReport};if(!await Wt(r,a))return;let b=_t(r),f=E.structUtils.parseRange(r.range).selector;if(!E.semverUtils.validRange(f)){let P=await o.getCandidates(r,new Map,d);f=E.structUtils.parseRange(P[0].reference).selector}let p=Ht.default.coerce(f);if(p===null)return;let h=`${Ie.suggestUtils.Modifier.CARET}${p.major}`,S=E.structUtils.makeDescriptor(E.structUtils.makeIdent("types",b),h),O=E.miscUtils.mapAndFind(n.workspaces,P=>{var T,V;let x=(T=P.manifest.dependencies.get(r.identHash))==null?void 0:T.descriptorHash,v=(V=P.manifest.devDependencies.get(r.identHash))==null?void 0:V.descriptorHash;if(x!==r.descriptorHash&&v!==r.descriptorHash)return E.miscUtils.mapAndFind.skip;let j=[];for(let Ae of Oe.Manifest.allDependencies){let Se=P.manifest[Ae].get(S.identHash);typeof Se!="undefined"&&j.push([Ae,Se])}return j.length===0?E.miscUtils.mapAndFind.skip:j});if(typeof O!="undefined")for(let[P,x]of O)e.manifest[P].set(x.identHash,x);else{try{if((await o.getCandidates(S,new Map,d)).length===0)return}catch{return}e.manifest[Ie.suggestUtils.Target.DEVELOPMENT].set(S.identHash,S)}},_s=async(e,t,r)=>{if(r.scope==="types")return;let s=_t(r),n=E.structUtils.makeIdent("types",s);for(let a of Oe.Manifest.allDependencies)typeof e.manifest[a].get(n.identHash)!="undefined"&&e.manifest[a].delete(n.identHash)},Fs=(e,t)=>{t.publishConfig&&t.publishConfig.typings&&(t.typings=t.publishConfig.typings),t.publishConfig&&t.publishConfig.types&&(t.types=t.publishConfig.types)},Bs={hooks:{afterWorkspaceDependencyAddition:Hs,afterWorkspaceDependencyRemoval:_s,beforeWorkspacePacking:Fs}},Ks=Bs;return Ws;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.0.1-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = (tsserver) => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const { isAbsolute } = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 22 | 23 | const dependencyTreeRoots = new Set( 24 | pnpApi.getDependencyTreeRoots().map((locator) => { 25 | return `${locator.name}@${locator.reference}`; 26 | }) 27 | ); 28 | 29 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 30 | // doesn't understand. This layer makes sure to remove the protocol 31 | // before forwarding it to TS, and to add it back on all returned paths. 32 | 33 | function toEditorPath(str) { 34 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 35 | if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { 36 | // We also take the opportunity to turn virtual paths into physical ones; 37 | // this makes it much easier to work with workspaces that list peer 38 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 39 | // file instances instead of the real ones. 40 | // 41 | // We only do this to modules owned by the the dependency tree roots. 42 | // This avoids breaking the resolution when jumping inside a vendor 43 | // with peer dep (otherwise jumping into react-dom would show resolution 44 | // errors on react). 45 | // 46 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 47 | if (resolved) { 48 | const locator = pnpApi.findPackageLocator(resolved); 49 | if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { 50 | str = resolved; 51 | } 52 | } 53 | 54 | str = normalize(str); 55 | 56 | if (str.match(/\.zip\//)) { 57 | switch (hostInfo) { 58 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 59 | // VSCode only adds it automatically for supported schemes, 60 | // so we have to do it manually for the `zip` scheme. 61 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 62 | // 63 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 64 | // 65 | case `vscode`: 66 | { 67 | str = `^zip:${str}`; 68 | } 69 | break; 70 | 71 | // To make "go to definition" work, 72 | // We have to resolve the actual file system path from virtual path 73 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 74 | case `coc-nvim`: 75 | { 76 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 77 | str = resolve(`zipfile:${str}`); 78 | } 79 | break; 80 | 81 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 82 | // We have to resolve the actual file system path from virtual path, 83 | // everything else is up to neovim 84 | case `neovim`: 85 | { 86 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 87 | str = `zipfile:${str}`; 88 | } 89 | break; 90 | 91 | default: 92 | { 93 | str = `zip:${str}`; 94 | } 95 | break; 96 | } 97 | } 98 | } 99 | 100 | return str; 101 | } 102 | 103 | function fromEditorPath(str) { 104 | return process.platform === `win32` 105 | ? str.replace(/^\^?zip:\//, ``) 106 | : str.replace(/^\^?zip:/, ``); 107 | } 108 | 109 | // Force enable 'allowLocalPluginLoads' 110 | // TypeScript tries to resolve plugins using a path relative to itself 111 | // which doesn't work when using the global cache 112 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 113 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 114 | // TypeScript already does local loads and if this code is running the user trusts the workspace 115 | // https://github.com/microsoft/vscode/issues/45856 116 | const ConfiguredProject = tsserver.server.ConfiguredProject; 117 | const { 118 | enablePluginsWithOptions: originalEnablePluginsWithOptions, 119 | } = ConfiguredProject.prototype; 120 | ConfiguredProject.prototype.enablePluginsWithOptions = function () { 121 | this.projectService.allowLocalPluginLoads = true; 122 | return originalEnablePluginsWithOptions.apply(this, arguments); 123 | }; 124 | 125 | // And here is the point where we hijack the VSCode <-> TS communications 126 | // by adding ourselves in the middle. We locate everything that looks 127 | // like an absolute path of ours and normalize it. 128 | 129 | const Session = tsserver.server.Session; 130 | const { onMessage: originalOnMessage, send: originalSend } = Session.prototype; 131 | let hostInfo = `unknown`; 132 | 133 | Object.assign(Session.prototype, { 134 | onMessage(/** @type {string} */ message) { 135 | const parsedMessage = JSON.parse(message); 136 | 137 | if ( 138 | parsedMessage != null && 139 | typeof parsedMessage === `object` && 140 | parsedMessage.arguments && 141 | typeof parsedMessage.arguments.hostInfo === `string` 142 | ) { 143 | hostInfo = parsedMessage.arguments.hostInfo; 144 | } 145 | 146 | return originalOnMessage.call( 147 | this, 148 | JSON.stringify(parsedMessage, (key, value) => { 149 | return typeof value === `string` ? fromEditorPath(value) : value; 150 | }) 151 | ); 152 | }, 153 | 154 | send(/** @type {any} */ msg) { 155 | return originalSend.call( 156 | this, 157 | JSON.parse( 158 | JSON.stringify(msg, (key, value) => { 159 | return typeof value === `string` ? toEditorPath(value) : value; 160 | }) 161 | ) 162 | ); 163 | }, 164 | }); 165 | 166 | return tsserver; 167 | }; 168 | 169 | if (existsSync(absPnpApiPath)) { 170 | if (!process.versions.pnp) { 171 | // Setup the environment to be able to require typescript/lib/tsserver.js 172 | require(absPnpApiPath).setup(); 173 | } 174 | } 175 | 176 | // Defer to the real typescript/lib/tsserver.js your application uses 177 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); 178 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserverlibrary.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = (tsserver) => { 13 | if (!process.versions.pnp) { 14 | return tsserver; 15 | } 16 | 17 | const { isAbsolute } = require(`path`); 18 | const pnpApi = require(`pnpapi`); 19 | 20 | const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); 21 | const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 22 | 23 | const dependencyTreeRoots = new Set( 24 | pnpApi.getDependencyTreeRoots().map((locator) => { 25 | return `${locator.name}@${locator.reference}`; 26 | }) 27 | ); 28 | 29 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 30 | // doesn't understand. This layer makes sure to remove the protocol 31 | // before forwarding it to TS, and to add it back on all returned paths. 32 | 33 | function toEditorPath(str) { 34 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 35 | if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { 36 | // We also take the opportunity to turn virtual paths into physical ones; 37 | // this makes it much easier to work with workspaces that list peer 38 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 39 | // file instances instead of the real ones. 40 | // 41 | // We only do this to modules owned by the the dependency tree roots. 42 | // This avoids breaking the resolution when jumping inside a vendor 43 | // with peer dep (otherwise jumping into react-dom would show resolution 44 | // errors on react). 45 | // 46 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 47 | if (resolved) { 48 | const locator = pnpApi.findPackageLocator(resolved); 49 | if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { 50 | str = resolved; 51 | } 52 | } 53 | 54 | str = normalize(str); 55 | 56 | if (str.match(/\.zip\//)) { 57 | switch (hostInfo) { 58 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 59 | // VSCode only adds it automatically for supported schemes, 60 | // so we have to do it manually for the `zip` scheme. 61 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 62 | // 63 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 64 | // 65 | case `vscode`: 66 | { 67 | str = `^zip:${str}`; 68 | } 69 | break; 70 | 71 | // To make "go to definition" work, 72 | // We have to resolve the actual file system path from virtual path 73 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 74 | case `coc-nvim`: 75 | { 76 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 77 | str = resolve(`zipfile:${str}`); 78 | } 79 | break; 80 | 81 | // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) 82 | // We have to resolve the actual file system path from virtual path, 83 | // everything else is up to neovim 84 | case `neovim`: 85 | { 86 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 87 | str = `zipfile:${str}`; 88 | } 89 | break; 90 | 91 | default: 92 | { 93 | str = `zip:${str}`; 94 | } 95 | break; 96 | } 97 | } 98 | } 99 | 100 | return str; 101 | } 102 | 103 | function fromEditorPath(str) { 104 | return process.platform === `win32` 105 | ? str.replace(/^\^?zip:\//, ``) 106 | : str.replace(/^\^?zip:/, ``); 107 | } 108 | 109 | // Force enable 'allowLocalPluginLoads' 110 | // TypeScript tries to resolve plugins using a path relative to itself 111 | // which doesn't work when using the global cache 112 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 113 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 114 | // TypeScript already does local loads and if this code is running the user trusts the workspace 115 | // https://github.com/microsoft/vscode/issues/45856 116 | const ConfiguredProject = tsserver.server.ConfiguredProject; 117 | const { 118 | enablePluginsWithOptions: originalEnablePluginsWithOptions, 119 | } = ConfiguredProject.prototype; 120 | ConfiguredProject.prototype.enablePluginsWithOptions = function () { 121 | this.projectService.allowLocalPluginLoads = true; 122 | return originalEnablePluginsWithOptions.apply(this, arguments); 123 | }; 124 | 125 | // And here is the point where we hijack the VSCode <-> TS communications 126 | // by adding ourselves in the middle. We locate everything that looks 127 | // like an absolute path of ours and normalize it. 128 | 129 | const Session = tsserver.server.Session; 130 | const { onMessage: originalOnMessage, send: originalSend } = Session.prototype; 131 | let hostInfo = `unknown`; 132 | 133 | Object.assign(Session.prototype, { 134 | onMessage(/** @type {string} */ message) { 135 | const parsedMessage = JSON.parse(message); 136 | 137 | if ( 138 | parsedMessage != null && 139 | typeof parsedMessage === `object` && 140 | parsedMessage.arguments && 141 | typeof parsedMessage.arguments.hostInfo === `string` 142 | ) { 143 | hostInfo = parsedMessage.arguments.hostInfo; 144 | } 145 | 146 | return originalOnMessage.call( 147 | this, 148 | JSON.stringify(parsedMessage, (key, value) => { 149 | return typeof value === `string` ? fromEditorPath(value) : value; 150 | }) 151 | ); 152 | }, 153 | 154 | send(/** @type {any} */ msg) { 155 | return originalSend.call( 156 | this, 157 | JSON.parse( 158 | JSON.stringify(msg, (key, value) => { 159 | return typeof value === `string` ? toEditorPath(value) : value; 160 | }) 161 | ) 162 | ); 163 | }, 164 | }); 165 | 166 | return tsserver; 167 | }; 168 | 169 | if (existsSync(absPnpApiPath)) { 170 | if (!process.versions.pnp) { 171 | // Setup the environment to be able to require typescript/lib/tsserverlibrary.js 172 | require(absPnpApiPath).setup(); 173 | } 174 | } 175 | 176 | // Defer to the real typescript/lib/tsserverlibrary.js your application uses 177 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); 178 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "3.8.3-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableGlobalCache: true 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 5 | spec: '@yarnpkg/plugin-typescript' 6 | 7 | yarnPath: .yarn/releases/yarn-3.0.0.cjs 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.2.1](https://github.com/merceyz/typescript-to-proptypes/compare/v2.2.0...v2.2.1) (2021-08-01) 6 | 7 | ### Bug Fixes 8 | 9 | - **parser:** handle more `elementType`s ([#34](https://github.com/merceyz/typescript-to-proptypes/issues/34)) ([472ce82](https://github.com/merceyz/typescript-to-proptypes/commit/472ce82e5565f38febbe11ebaa7dbba00993a064)) 10 | - **parser:** support retrieving type of destructured forwardRef arg ([#33](https://github.com/merceyz/typescript-to-proptypes/issues/33)) ([b554187](https://github.com/merceyz/typescript-to-proptypes/commit/b554187d2d804658001ac0315be61266ce8af295)) 11 | 12 | ## [2.2.0](https://github.com/merceyz/typescript-to-proptypes/compare/v2.1.2...v2.2.0) (2020-09-14) 13 | 14 | ### Features 15 | 16 | - add TS@3.8 support ([#29](https://github.com/merceyz/typescript-to-proptypes/issues/29)) ([40f0358](https://github.com/merceyz/typescript-to-proptypes/commit/40f0358bc691e3c5b892276626d9dc554b4c2b6e)) 17 | 18 | ### Performance Improvements 19 | 20 | - **parser:** use set instead of array for stack ([426dd39](https://github.com/merceyz/typescript-to-proptypes/commit/426dd39bd17c5fb1990c0e023d108bc9df1d8b10)) 21 | 22 | ### [2.1.2](https://github.com/merceyz/typescript-to-proptypes/compare/v2.1.1...v2.1.2) (2020-09-13) 23 | 24 | ### Bug Fixes 25 | 26 | - **parser:** parse Date type ([bb94047](https://github.com/merceyz/typescript-to-proptypes/commit/bb94047456e7932863adc516cb0e2b9b689f328a)), closes [#30](https://github.com/merceyz/typescript-to-proptypes/issues/30) 27 | 28 | ### [2.1.1](https://github.com/merceyz/typescript-to-proptypes/compare/v2.1.0...v2.1.1) (2020-08-17) 29 | 30 | ### Bug Fixes 31 | 32 | - **injector:** check previous validator source for string literal keys ([5d06cfb](https://github.com/merceyz/typescript-to-proptypes/commit/5d06cfb0373d43b7a9c19fa2bb456b5efbb38e50)) 33 | 34 | ## [2.1.0](https://github.com/merceyz/typescript-to-proptypes/compare/v2.0.1...v2.1.0) (2020-06-18) 35 | 36 | ### Features 37 | 38 | - make literal sort configurable ([#25](https://github.com/merceyz/typescript-to-proptypes/issues/25)) ([d02a08a](https://github.com/merceyz/typescript-to-proptypes/commit/d02a08a7f4c7a25e08c786fa8b625d838613c8b5)) 39 | 40 | ### [2.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v2.0.0...v2.0.1) (2020-06-02) 41 | 42 | ### Bug Fixes 43 | 44 | - Use symbol type when there's no baseconstraint ([#23](https://github.com/merceyz/typescript-to-proptypes/issues/23)) ([0b170af](https://github.com/merceyz/typescript-to-proptypes/commit/0b170afb02a2edd1ea0b80406f1a86375c3a13f3)) 45 | 46 | ## [2.0.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.5.0...v2.0.0) (2020-05-31) 47 | 48 | ### ⚠ BREAKING CHANGES 49 | 50 | - Support for Node versions less than 10.3.0 has been dropped 51 | 52 | ### Features 53 | 54 | - consider squashed call signatures of function components ([#20](https://github.com/merceyz/typescript-to-proptypes/issues/20)) ([514d8ed](https://github.com/merceyz/typescript-to-proptypes/commit/514d8ed55375406a70640d64c4a166aa52e24ae2)) 55 | 56 | ### Bug Fixes 57 | 58 | - allow non-string literals ([#21](https://github.com/merceyz/typescript-to-proptypes/issues/21)) ([546e7ad](https://github.com/merceyz/typescript-to-proptypes/commit/546e7addc86198e641d3bfd3dd08ecb55c970600)) 59 | 60 | ### Build System 61 | 62 | - drop support for node versions less than 10.3.0 ([2fbca64](https://github.com/merceyz/typescript-to-proptypes/commit/2fbca64e0964509e1a74d29f564be41a78e9fa29)) 63 | 64 | ## [1.5.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.2...v1.5.0) (2020-04-06) 65 | 66 | ### Features 67 | 68 | - **injector:** add reconcilePropTypes ([#10](https://github.com/merceyz/typescript-to-proptypes/issues/10)) ([7b0bff9](https://github.com/merceyz/typescript-to-proptypes/commit/7b0bff9666d1beb1bde445e92fbb702cf1fb3d89)) 69 | - add `filenames` to component and proptype nodes ([#9](https://github.com/merceyz/typescript-to-proptypes/issues/9)) ([ce9a700](https://github.com/merceyz/typescript-to-proptypes/commit/ce9a7002c7fda27965b50e0b1af3ecef540a90e5)) 70 | - **injector:** add `component` to `shouldInclude` ([#8](https://github.com/merceyz/typescript-to-proptypes/issues/8)) ([18a7fce](https://github.com/merceyz/typescript-to-proptypes/commit/18a7fcee1b3f7d64541fb0f9bd1de72e0ea0db5b)) 71 | - **injector:** allow providing babel options ([2ab6f43](https://github.com/merceyz/typescript-to-proptypes/commit/2ab6f43ef4b785d20dd6f951b2f4b928a5521b53)) 72 | 73 | ### Bug Fixes 74 | 75 | - check nodeType for dom elements ([#13](https://github.com/merceyz/typescript-to-proptypes/issues/13)) ([fd028e6](https://github.com/merceyz/typescript-to-proptypes/commit/fd028e639bb28383d6e4f925368b6e2afacdbf23)) 76 | - replace existing propTypes when removeExistingPropTypes ([#15](https://github.com/merceyz/typescript-to-proptypes/issues/15)) ([3166104](https://github.com/merceyz/typescript-to-proptypes/commit/3166104889d4f58fc22f85800664d2bb1fce6aff)) 77 | - **injector:** always call injectPropTypes to allow shouldInclude to run ([277258d](https://github.com/merceyz/typescript-to-proptypes/commit/277258ddc73c3da816aba6fccb739c69dfe8e83a)) 78 | - handle all props getting ignored by shouldInclude ([b69112e](https://github.com/merceyz/typescript-to-proptypes/commit/b69112e1011f089b6d5cb60f88ce75b6394252be)) 79 | - **parser:** export ParserOptions ([3a5d55e](https://github.com/merceyz/typescript-to-proptypes/commit/3a5d55e68a723208a4b76e79d4bafe92ddf4f85a)) 80 | 81 | ### [1.4.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.1...v1.4.2) (2020-03-27) 82 | 83 | ### Bug Fixes 84 | 85 | - build had a broken output ([97b0326](https://github.com/merceyz/typescript-to-proptypes/commit/97b0326c8b3b811fd5167cefa95a5dc1aa22a212)) 86 | 87 | ### [1.4.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.4.0...v1.4.1) (2020-03-27) 88 | 89 | ### Bug Fixes 90 | 91 | - include string literal object keys as used ([#5](https://github.com/merceyz/typescript-to-proptypes/issues/5)) ([3fd7b70](https://github.com/merceyz/typescript-to-proptypes/commit/3fd7b703d30e650e6692f87d3929d4ae67314cb6)) 92 | - unknown can be optional ([#7](https://github.com/merceyz/typescript-to-proptypes/issues/7)) ([c5e8ca3](https://github.com/merceyz/typescript-to-proptypes/commit/c5e8ca31e2cae20216b1f7e45c9f3ef5198b2f93)) 93 | 94 | ## [1.4.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.3.0...v1.4.0) (2019-11-16) 95 | 96 | ### Bug Fixes 97 | 98 | - **parser:** handle prop of type ReactElement ([adfcca4](https://github.com/merceyz/typescript-to-proptypes/commit/adfcca4)) 99 | 100 | ### Features 101 | 102 | - **parser:** support forwardRef ([3f5c0c9](https://github.com/merceyz/typescript-to-proptypes/commit/3f5c0c9)), closes [#2](https://github.com/merceyz/typescript-to-proptypes/issues/2) 103 | 104 | ## [1.3.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.5...v1.3.0) (2019-09-03) 105 | 106 | ### Features 107 | 108 | - **generator:** add comment to proptype blocks ([2c5627e](https://github.com/merceyz/typescript-to-proptypes/commit/2c5627e)) 109 | 110 | ### [1.2.5](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.4...v1.2.5) (2019-09-03) 111 | 112 | ### Bug Fixes 113 | 114 | - **parser:** use doctrine to unwrap comments ([53a9d43](https://github.com/merceyz/typescript-to-proptypes/commit/53a9d43)) 115 | 116 | ### Tests 117 | 118 | - add missing test config ([d00c7f2](https://github.com/merceyz/typescript-to-proptypes/commit/d00c7f2)) 119 | 120 | ### [1.2.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.3...v1.2.4) (2019-08-16) 121 | 122 | ### Bug Fixes 123 | 124 | - **injector:** use require.resolve ([b9d04ea](https://github.com/merceyz/typescript-to-proptypes/commit/b9d04ea)) 125 | 126 | ### [1.2.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.2...v1.2.3) (2019-07-24) 127 | 128 | ### Bug Fixes 129 | 130 | - **parser:** handle return type of JSX.Element | null ([cbe5564](https://github.com/merceyz/typescript-to-proptypes/commit/cbe5564)) 131 | 132 | ### [1.2.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.1...v1.2.2) (2019-07-23) 133 | 134 | ### Bug Fixes 135 | 136 | - **parser:** remove leftover asterisk ([2e720df](https://github.com/merceyz/typescript-to-proptypes/commit/2e720df)) 137 | 138 | ### [1.2.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.2.0...v1.2.1) (2019-07-23) 139 | 140 | ### Bug Fixes 141 | 142 | - **parser:** handle single line comments ([0025d53](https://github.com/merceyz/typescript-to-proptypes/commit/0025d53)) 143 | 144 | ## [1.2.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.1.0...v1.2.0) (2019-07-23) 145 | 146 | ### Bug Fixes 147 | 148 | - **generator:** multiline comments ([d576597](https://github.com/merceyz/typescript-to-proptypes/commit/d576597)) 149 | - **generator:** sort interface correctly ([f88c5fb](https://github.com/merceyz/typescript-to-proptypes/commit/f88c5fb)) 150 | - **generator:** wrap prop name in quotes ([709a819](https://github.com/merceyz/typescript-to-proptypes/commit/709a819)) 151 | - **parser:** don't modify comments ([95cd63e](https://github.com/merceyz/typescript-to-proptypes/commit/95cd63e)) 152 | - **parser:** fallback to object if element is undefined ([eadaf3f](https://github.com/merceyz/typescript-to-proptypes/commit/eadaf3f)) 153 | - **parser:** handle comments with just tags ([d0b0a82](https://github.com/merceyz/typescript-to-proptypes/commit/d0b0a82)) 154 | - **parser:** handle comments with tags ([ad4dddd](https://github.com/merceyz/typescript-to-proptypes/commit/ad4dddd)) 155 | - **parser:** handle optional any ([30f56ec](https://github.com/merceyz/typescript-to-proptypes/commit/30f56ec)) 156 | - **parser:** handle optional React.ElementType ([c7a87fd](https://github.com/merceyz/typescript-to-proptypes/commit/c7a87fd)) 157 | - **parser:** treat ComponentType as elementType ([53f1e21](https://github.com/merceyz/typescript-to-proptypes/commit/53f1e21)) 158 | - export typescript as ts ([ba90e22](https://github.com/merceyz/typescript-to-proptypes/commit/ba90e22)) 159 | 160 | ### Features 161 | 162 | - **generator:** support instanceOf ([6bd563a](https://github.com/merceyz/typescript-to-proptypes/commit/6bd563a)) 163 | - **injector:** control included props ([4f8eaa1](https://github.com/merceyz/typescript-to-proptypes/commit/4f8eaa1)) 164 | - **injector:** remove existing proptypes ([d2a978c](https://github.com/merceyz/typescript-to-proptypes/commit/d2a978c)) 165 | - **parser:** check const declarations of React.ComponentType ([cbd2eb6](https://github.com/merceyz/typescript-to-proptypes/commit/cbd2eb6)) 166 | - **parser:** handle React.Component and Element instanceOf ([570d73b](https://github.com/merceyz/typescript-to-proptypes/commit/570d73b)) 167 | - **parser:** support elementType ([448d5a6](https://github.com/merceyz/typescript-to-proptypes/commit/448d5a6)) 168 | 169 | ## [1.1.0](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.4...v1.1.0) (2019-07-15) 170 | 171 | ### Bug Fixes 172 | 173 | - **generator:** don't pass shouldInclude on interfaceNode ([1302502](https://github.com/merceyz/typescript-to-proptypes/commit/1302502)) 174 | 175 | ### Features 176 | 177 | - **parser:** circular references ([7de51cc](https://github.com/merceyz/typescript-to-proptypes/commit/7de51cc)) 178 | - **parser:** control included proptypes ([2952e78](https://github.com/merceyz/typescript-to-proptypes/commit/2952e78)) 179 | - **parser:** objects / shapes ([81f1a82](https://github.com/merceyz/typescript-to-proptypes/commit/81f1a82)) 180 | 181 | ### [1.0.4](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.3...v1.0.4) (2019-07-10) 182 | 183 | ### Bug Fixes 184 | 185 | - **generator:** omit null if proptype is optional ([21351a4](https://github.com/merceyz/typescript-to-proptypes/commit/21351a4)) 186 | - **parser:** reactnode should make proptype optional ([c84b611](https://github.com/merceyz/typescript-to-proptypes/commit/c84b611)) 187 | 188 | ### [1.0.3](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.2...v1.0.3) (2019-07-10) 189 | 190 | ### Bug Fixes 191 | 192 | - export types ([7583291](https://github.com/merceyz/typescript-to-proptypes/commit/7583291)) 193 | 194 | ### [1.0.2](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.1...v1.0.2) (2019-07-09) 195 | 196 | ### Bug Fixes 197 | 198 | - **injector:** don't visit FunctionDeclarations more than once ([236276b](https://github.com/merceyz/typescript-to-proptypes/commit/236276b)) 199 | 200 | ### [1.0.1](https://github.com/merceyz/typescript-to-proptypes/compare/v1.0.0...v1.0.1) (2019-07-09) 201 | 202 | ### Bug Fixes 203 | 204 | - **injector:** don't import prop-types if it's already imported ([9d4dfd1](https://github.com/merceyz/typescript-to-proptypes/commit/9d4dfd1)) 205 | - **injector:** insert import after the first one ([6cb31a0](https://github.com/merceyz/typescript-to-proptypes/commit/6cb31a0)) 206 | 207 | ## 1.0.0 (2019-07-08) 208 | 209 | ### Build System 210 | 211 | - disable incremental ([37b0277](https://github.com/merceyz/typescript-to-proptypes/commit/37b0277)) 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kristoffer K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-to-proptypes 2 | 3 | An API for converting [TypeScript](https://www.npmjs.com/package/typescript) definitions to [PropTypes](https://www.npmjs.com/package/prop-types) using the TypeScript Compiler API 4 | 5 | ## Install 6 | 7 | ``` 8 | yarn add typescript-to-proptypes --dev 9 | or 10 | npm install typescript-to-proptypes --save-dev 11 | ``` 12 | 13 | ## Support 14 | 15 | | Component type | | 16 | | ---------------- | ------------------ | 17 | | Class | :heavy_check_mark: | 18 | | Function | :heavy_check_mark: | 19 | | Const functions | :heavy_check_mark: | 20 | | React.memo | :heavy_check_mark: | 21 | | React.ForwardRef | :heavy_check_mark: | 22 | 23 | ## License 24 | 25 | This project is licensed under the terms of the [MIT license](/LICENSE). 26 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('ts-jest/presets'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | testEnvironment: 'node', 6 | transform: defaults.transform, 7 | testRegex: `test/index.test.ts$`, 8 | globals: { 9 | 'ts-jest': { 10 | packageJson: path.join(__dirname, 'package.json'), 11 | }, 12 | }, 13 | coveragePathIgnorePatterns: ['/node_modules/', '/.pnp.cjs'], 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-to-proptypes", 3 | "packageManager": "yarn@3.0.0", 4 | "version": "2.2.1", 5 | "description": "Generate proptypes from typescript declarations", 6 | "main": "dist/index.js", 7 | "engines": { 8 | "node": ">=10.3.0" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/merceyz/typescript-to-proptypes.git" 16 | }, 17 | "author": "merceyz ", 18 | "license": "MIT", 19 | "keywords": [ 20 | "proptypes", 21 | "typescript", 22 | "react" 23 | ], 24 | "scripts": { 25 | "test": "jest", 26 | "build": "rm -rf dist && tsc", 27 | "release": "run build && standard-version", 28 | "prepack": "run build" 29 | }, 30 | "devDependencies": { 31 | "@types/babel__core": "^7.1.2", 32 | "@types/doctrine": "^0.0.3", 33 | "@types/glob": "^7.1.1", 34 | "@types/jest": "^24.0.15", 35 | "@types/lodash": "^4.14.136", 36 | "@types/node": "^12.6.2", 37 | "@types/prettier": "^1.19.1", 38 | "@types/react": "^16.8.23", 39 | "@types/uuid": "^8.0.0", 40 | "glob": "^7.1.6", 41 | "husky": "^4.2.3", 42 | "jest": "^26.0.1", 43 | "prettier": "^2.0.1", 44 | "pretty-quick": "^2.0.1", 45 | "react": "^16.8.6", 46 | "standard-version": "^8.0.0", 47 | "ts-jest": "^26.1.0" 48 | }, 49 | "dependencies": { 50 | "@babel/core": "^7.11.1", 51 | "@babel/plugin-syntax-class-properties": "^7.10.4", 52 | "@babel/plugin-syntax-jsx": "^7.10.4", 53 | "@babel/types": "^7.11.0", 54 | "doctrine": "^3.0.0", 55 | "lodash": "^4.17.14", 56 | "tslib": "^1.13.0", 57 | "typescript": "3.8.3", 58 | "uuid": "^8.1.0" 59 | }, 60 | "dependenciesMeta": { 61 | "core-js": { 62 | "built": false 63 | } 64 | }, 65 | "husky": { 66 | "hooks": { 67 | "pre-commit": "yarn pretty-quick --staged" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | 5 | /** 6 | * Loads and parses a `tsconfig` file and returns a `ts.CompilerOptions` object 7 | * @param tsConfigPath The location for a `tsconfig.json` file 8 | */ 9 | export function loadConfig(tsConfigPath: string) { 10 | const { config, error } = ts.readConfigFile(tsConfigPath, (filePath) => 11 | fs.readFileSync(filePath).toString() 12 | ); 13 | 14 | if (error) throw error; 15 | 16 | const { options, errors } = ts.parseJsonConfigFileContent( 17 | config, 18 | ts.sys, 19 | path.dirname(tsConfigPath) 20 | ); 21 | 22 | if (errors.length > 0) throw errors[0]; 23 | 24 | return options; 25 | } 26 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import * as t from './types'; 2 | import _ from 'lodash'; 3 | 4 | export interface GenerateOptions { 5 | /** 6 | * Enable/disable the default sorting (ascending) or provide your own sort function 7 | * @default true 8 | */ 9 | sortProptypes?: boolean | ((a: t.PropTypeNode, b: t.PropTypeNode) => 0 | -1 | 1); 10 | 11 | /** 12 | * The name used when importing prop-types 13 | * @default 'PropTypes' 14 | */ 15 | importedName?: string; 16 | 17 | /** 18 | * Enable/disable including JSDoc comments 19 | * @default true 20 | */ 21 | includeJSDoc?: boolean; 22 | 23 | /** 24 | * Previous source code of the validator for each prop type 25 | */ 26 | previousPropTypesSource?: Map; 27 | 28 | /** 29 | * Given the `prop`, the `previous` source of the validator and the `generated` source: 30 | * What source should be injected? `previous` is `undefined` if the validator 31 | * didn't exist before 32 | * @default Uses `generated` source 33 | */ 34 | reconcilePropTypes?( 35 | proptype: t.PropTypeNode, 36 | previous: string | undefined, 37 | generated: string 38 | ): string; 39 | 40 | /** 41 | * Control which PropTypes are included in the final result 42 | * @param proptype The current PropType about to be converted to text 43 | */ 44 | shouldInclude?(proptype: t.PropTypeNode): boolean | undefined; 45 | 46 | /** 47 | * A comment that will be added to the start of the PropTypes code block 48 | * @example 49 | * foo.propTypes = { 50 | * // Comment goes here 51 | * } 52 | */ 53 | comment?: string; 54 | 55 | /** 56 | * Overrides the given `sortLiteralUnions` based on the proptype. 57 | * If `undefined` is returned the default `sortLiteralUnions` will be used. 58 | */ 59 | getSortLiteralUnions?: ( 60 | component: t.ComponentNode, 61 | propType: t.PropTypeNode 62 | ) => ((a: t.LiteralNode, b: t.LiteralNode) => number) | undefined; 63 | 64 | /** 65 | * By default literals in unions are sorted by: 66 | * - numbers last, ascending 67 | * - anything else by their stringified value using localeCompare 68 | */ 69 | sortLiteralUnions?: (a: t.LiteralNode, b: t.LiteralNode) => number; 70 | 71 | /** 72 | * The component of the given `node`. 73 | * Must be defined for anything but programs and components 74 | */ 75 | component?: t.ComponentNode; 76 | } 77 | 78 | function defaultSortLiteralUnions(a: t.LiteralNode, b: t.LiteralNode) { 79 | const { value: valueA } = a; 80 | const { value: valueB } = b; 81 | // numbers ascending 82 | if (typeof valueA === 'number' && typeof valueB === 'number') { 83 | return valueA - valueB; 84 | } 85 | // numbers last 86 | if (typeof valueA === 'number') { 87 | return 1; 88 | } 89 | if (typeof valueB === 'number') { 90 | return -1; 91 | } 92 | // sort anything else by their stringified value 93 | return String(valueA).localeCompare(String(valueB)); 94 | } 95 | 96 | /** 97 | * Generates code from the given node 98 | * @param node The node to convert to code 99 | * @param options The options used to control the way the code gets generated 100 | */ 101 | export function generate(node: t.Node | t.PropTypeNode[], options: GenerateOptions = {}): string { 102 | const { 103 | component, 104 | sortProptypes = true, 105 | importedName = 'PropTypes', 106 | includeJSDoc = true, 107 | previousPropTypesSource = new Map(), 108 | reconcilePropTypes = (_prop: t.PropTypeNode, _previous: string, generated: string) => generated, 109 | shouldInclude, 110 | getSortLiteralUnions = () => defaultSortLiteralUnions, 111 | sortLiteralUnions = defaultSortLiteralUnions, 112 | } = options; 113 | 114 | function jsDoc(node: t.PropTypeNode | t.LiteralNode) { 115 | if (!includeJSDoc || !node.jsDoc) { 116 | return ''; 117 | } 118 | return `/**\n* ${node.jsDoc.split(/\r?\n/).reduce((prev, curr) => prev + '\n* ' + curr)}\n*/\n`; 119 | } 120 | 121 | if (Array.isArray(node)) { 122 | let propTypes = node; 123 | 124 | if (typeof sortProptypes === 'function') { 125 | propTypes = propTypes.sort(sortProptypes); 126 | } else if (sortProptypes === true) { 127 | propTypes = propTypes.sort((a, b) => a.name.localeCompare(b.name)); 128 | } 129 | 130 | let filteredNodes = node; 131 | if (shouldInclude) { 132 | filteredNodes = filteredNodes.filter((x) => shouldInclude(x)); 133 | } 134 | 135 | if (filteredNodes.length === 0) { 136 | return ''; 137 | } 138 | 139 | return filteredNodes 140 | .map((prop) => generate(prop, options)) 141 | .reduce((prev, curr) => `${prev}\n${curr}`); 142 | } 143 | 144 | if (t.isProgramNode(node)) { 145 | return node.body 146 | .map((prop) => generate(prop, options)) 147 | .reduce((prev, curr) => `${prev}\n${curr}`); 148 | } 149 | 150 | if (t.isComponentNode(node)) { 151 | const generated = generate(node.types, { ...options, component: node }); 152 | if (generated.length === 0) { 153 | return ''; 154 | } 155 | 156 | const comment = 157 | options.comment && 158 | `// ${options.comment.split(/\r?\n/gm).reduce((prev, curr) => `${prev}\n// ${curr}`)}\n`; 159 | 160 | return `${node.name}.propTypes = {\n${comment ? comment : ''}${generated}\n}`; 161 | } 162 | 163 | if (component === undefined) { 164 | throw new TypeError('Missing component context. This is likely a bug. Please open an issue.'); 165 | } 166 | 167 | if (t.isPropTypeNode(node)) { 168 | let isOptional = false; 169 | let propType = { ...node.propType }; 170 | 171 | if (t.isUnionNode(propType) && propType.types.some(t.isUndefinedNode)) { 172 | isOptional = true; 173 | propType.types = propType.types.filter( 174 | (prop) => !t.isUndefinedNode(prop) && !(t.isLiteralNode(prop) && prop.value === 'null') 175 | ); 176 | if (propType.types.length === 1 && t.isLiteralNode(propType.types[0]) === false) { 177 | propType = propType.types[0]; 178 | } 179 | } 180 | 181 | if (t.isDOMElementNode(propType)) { 182 | propType.optional = isOptional; 183 | // Handled internally in the validate function 184 | isOptional = true; 185 | } 186 | 187 | const validatorSource = reconcilePropTypes( 188 | node, 189 | previousPropTypesSource.get(node.name), 190 | `${generate(propType, { 191 | ...options, 192 | sortLiteralUnions: getSortLiteralUnions(component, node) || sortLiteralUnions, 193 | })}${isOptional ? '' : '.isRequired'}` 194 | ); 195 | 196 | return `${jsDoc(node)}"${node.name}": ${validatorSource},`; 197 | } 198 | 199 | if (t.isInterfaceNode(node)) { 200 | return `${importedName}.shape({\n${generate(node.types, { 201 | ...options, 202 | shouldInclude: undefined, 203 | })}\n})`; 204 | } 205 | 206 | if (t.isFunctionNode(node)) { 207 | return `${importedName}.func`; 208 | } 209 | 210 | if (t.isStringNode(node)) { 211 | return `${importedName}.string`; 212 | } 213 | 214 | if (t.isBooleanNode(node)) { 215 | return `${importedName}.bool`; 216 | } 217 | 218 | if (t.isNumericNode(node)) { 219 | return `${importedName}.number`; 220 | } 221 | 222 | if (t.isLiteralNode(node)) { 223 | return `${importedName}.oneOf([${jsDoc(node)}${node.value}])`; 224 | } 225 | 226 | if (t.isObjectNode(node)) { 227 | return `${importedName}.object`; 228 | } 229 | 230 | if (t.isAnyNode(node)) { 231 | return `${importedName}.any`; 232 | } 233 | 234 | if (t.isElementNode(node)) { 235 | return `${importedName}.${node.elementType}`; 236 | } 237 | 238 | if (t.isInstanceOfNode(node)) { 239 | return `${importedName}.instanceOf(${node.instance})`; 240 | } 241 | 242 | if (t.isDOMElementNode(node)) { 243 | return `function (props, propName) { 244 | if (props[propName] == null) { 245 | return ${ 246 | node.optional 247 | ? 'null' 248 | : `new Error("Prop '" + propName + "' is required but wasn't specified")` 249 | } 250 | } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { 251 | return new Error("Expected prop '" + propName + "' to be of type Element") 252 | } 253 | }`; 254 | } 255 | 256 | if (t.isArrayNode(node)) { 257 | if (t.isAnyNode(node.arrayType)) { 258 | return `${importedName}.array`; 259 | } 260 | 261 | return `${importedName}.arrayOf(${generate(node.arrayType, options)})`; 262 | } 263 | 264 | if (t.isUnionNode(node)) { 265 | let [literals, rest] = _.partition(t.uniqueUnionTypes(node).types, t.isLiteralNode); 266 | 267 | literals = literals.sort(sortLiteralUnions); 268 | 269 | const nodeToStringName = (obj: t.Node): string => { 270 | if (t.isInstanceOfNode(obj)) { 271 | return `${obj.type}.${obj.instance}`; 272 | } else if (t.isInterfaceNode(obj)) { 273 | // An interface is PropTypes.shape 274 | // Use `ShapeNode` to get it sorted in the correct order 275 | return `ShapeNode`; 276 | } 277 | 278 | return obj.type; 279 | }; 280 | 281 | rest = rest.sort((a, b) => nodeToStringName(a).localeCompare(nodeToStringName(b))); 282 | 283 | if (literals.find((x) => x.value === 'true') && literals.find((x) => x.value === 'false')) { 284 | rest.push(t.booleanNode()); 285 | literals = literals.filter((x) => x.value !== 'true' && x.value !== 'false'); 286 | } 287 | 288 | const literalProps = 289 | literals.length !== 0 290 | ? `${importedName}.oneOf([${literals 291 | .map((x) => `${jsDoc(x)}${x.value}`) 292 | .reduce((prev, curr) => `${prev},${curr}`)}])` 293 | : ''; 294 | 295 | if (rest.length === 0) { 296 | return literalProps; 297 | } 298 | 299 | if (literals.length === 0 && rest.length === 1) { 300 | return generate(rest[0], options); 301 | } 302 | 303 | return `${importedName}.oneOfType([${literalProps ? literalProps + ', ' : ''}${rest 304 | .map((x) => generate(x, options)) 305 | .reduce((prev, curr) => `${prev},${curr}`)}])`; 306 | } 307 | 308 | throw new Error(`Nothing to handle node of type "${node.type}"`); 309 | } 310 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './generator'; 3 | export * from './parser'; 4 | export * from './injector'; 5 | export * from './types'; 6 | 7 | import * as ts from 'typescript'; 8 | export { ts }; 9 | -------------------------------------------------------------------------------- /src/injector.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import * as babelTypes from '@babel/types'; 3 | import * as t from './types/index'; 4 | import { generate, GenerateOptions } from './generator'; 5 | import { v4 as uuid } from 'uuid'; 6 | 7 | export type InjectOptions = { 8 | /** 9 | * By default all unused props are omitted from the result. 10 | * Set this to true to include them instead. 11 | */ 12 | includeUnusedProps?: boolean; 13 | /** 14 | * By default existing PropTypes are left alone, set this to true 15 | * to have them removed before injecting the PropTypes 16 | */ 17 | removeExistingPropTypes?: boolean; 18 | /** 19 | * Used to control which props are includes in the result 20 | * @return true to include the prop, false to skip it, or undefined to 21 | * use the default behaviour 22 | * @default includeUnusedProps ? true : data.usedProps.includes(data.prop.name) 23 | */ 24 | shouldInclude?(data: { 25 | component: t.ComponentNode; 26 | prop: t.PropTypeNode; 27 | usedProps: string[]; 28 | }): boolean | undefined; 29 | 30 | /** 31 | * You can override the order of literals in unions based on the proptype. 32 | * 33 | * By default literals in unions are sorted by: 34 | * - numbers last, ascending 35 | * - anything else by their stringified value using localeCompare 36 | * Note: The order of the literals as they "appear" in the typings cannot be preserved. 37 | * Sometimes the type checker preserves it, sometimes it doesn't. 38 | * By always returning 0 from the sort function you keep the order the type checker dictates. 39 | */ 40 | getSortLiteralUnions?: ( 41 | component: t.ComponentNode, 42 | propType: t.PropTypeNode 43 | ) => ((a: t.LiteralNode, b: t.LiteralNode) => number) | undefined; 44 | 45 | /** 46 | * Options passed to babel.transformSync 47 | */ 48 | babelOptions?: babel.TransformOptions; 49 | } & Pick; 50 | 51 | /** 52 | * Injects the PropTypes from `parse` into the provided JavaScript code 53 | * @param propTypes Result from `parse` to inject into the JavaScript code 54 | * @param target The JavaScript code to add the PropTypes to 55 | * @param options Options controlling the final result 56 | */ 57 | export function inject( 58 | propTypes: t.ProgramNode, 59 | target: string, 60 | options: InjectOptions = {} 61 | ): string | null { 62 | if (propTypes.body.length === 0) { 63 | return target; 64 | } 65 | 66 | const propTypesToInject = new Map(); 67 | 68 | const { plugins: babelPlugins = [], ...babelOptions } = options.babelOptions || {}; 69 | 70 | const result = babel.transformSync(target, { 71 | plugins: [ 72 | require.resolve('@babel/plugin-syntax-class-properties'), 73 | require.resolve('@babel/plugin-syntax-jsx'), 74 | plugin(propTypes, options, propTypesToInject), 75 | ...(babelPlugins || []), 76 | ], 77 | configFile: false, 78 | babelrc: false, 79 | retainLines: true, 80 | ...babelOptions, 81 | }); 82 | 83 | let code = result && result.code; 84 | if (!code) { 85 | return null; 86 | } 87 | 88 | // Replace the placeholders with the generated prop-types 89 | // Workaround for issues with comments getting removed and malformed 90 | propTypesToInject.forEach((value, key) => { 91 | code = code!.replace(key, `\n\n${value}\n\n`); 92 | }); 93 | 94 | return code; 95 | } 96 | 97 | function plugin( 98 | propTypes: t.ProgramNode, 99 | options: InjectOptions = {}, 100 | mapOfPropTypes: Map 101 | ): babel.PluginObj { 102 | const { 103 | includeUnusedProps = false, 104 | reconcilePropTypes = ( 105 | _prop: t.PropTypeNode, 106 | _previous: string | undefined, 107 | generated: string 108 | ) => generated, 109 | removeExistingPropTypes = false, 110 | ...otherOptions 111 | } = options; 112 | const shouldInclude: Exclude = (data) => { 113 | if (options.shouldInclude) { 114 | const result = options.shouldInclude(data); 115 | if (result !== undefined) { 116 | return result; 117 | } 118 | } 119 | 120 | return includeUnusedProps ? true : data.usedProps.includes(data.prop.name); 121 | }; 122 | 123 | let importName = ''; 124 | let needImport = false; 125 | let alreadyImported = false; 126 | let originalPropTypesPath: null | babel.NodePath = null; 127 | let previousPropTypesSource = new Map(); 128 | 129 | return { 130 | visitor: { 131 | Program: { 132 | enter(path, state: any) { 133 | if ( 134 | !path.node.body.some((n) => { 135 | if ( 136 | babelTypes.isImportDeclaration(n) && 137 | n.source.value === 'prop-types' && 138 | n.specifiers.length 139 | ) { 140 | importName = n.specifiers[0].local.name; 141 | alreadyImported = true; 142 | return true; 143 | } 144 | }) 145 | ) { 146 | importName = 'PropTypes'; 147 | } 148 | 149 | path.get('body').forEach((nodePath) => { 150 | const { node } = nodePath; 151 | if ( 152 | babelTypes.isExpressionStatement(node) && 153 | babelTypes.isAssignmentExpression(node.expression, { operator: '=' }) && 154 | babelTypes.isMemberExpression(node.expression.left) && 155 | babelTypes.isIdentifier(node.expression.left.property, { name: 'propTypes' }) 156 | ) { 157 | originalPropTypesPath = nodePath; 158 | 159 | if (babelTypes.isObjectExpression(node.expression.right)) { 160 | const { code } = state.file; 161 | 162 | node.expression.right.properties.forEach((property) => { 163 | if (babelTypes.isObjectProperty(property)) { 164 | const validatorSource = code.slice(property.value.start, property.value.end); 165 | if (babelTypes.isIdentifier(property.key)) { 166 | previousPropTypesSource.set(property.key.name, validatorSource); 167 | } else if (babelTypes.isStringLiteral(property.key)) { 168 | previousPropTypesSource.set(property.key.value, validatorSource); 169 | } else { 170 | console.warn( 171 | `${state.filename}: Possibly missed original proTypes source. Can only determine names for 'Identifiers' and 'StringLiteral' but received '${property.key.type}'.` 172 | ); 173 | } 174 | } 175 | }); 176 | } 177 | } 178 | }); 179 | }, 180 | exit(path) { 181 | if (alreadyImported || !needImport) return; 182 | 183 | const propTypesImport = babel.template.ast( 184 | `import ${importName} from 'prop-types'` 185 | ) as babel.types.ImportDeclaration; 186 | 187 | const firstImport = path 188 | .get('body') 189 | .find((nodePath) => babelTypes.isImportDeclaration(nodePath.node)); 190 | 191 | // Insert import after the first one to avoid issues with comment flags 192 | if (firstImport) { 193 | firstImport.insertAfter(propTypesImport); 194 | } else { 195 | path.node.body = [propTypesImport, ...path.node.body]; 196 | } 197 | }, 198 | }, 199 | FunctionDeclaration(path) { 200 | const { node } = path; 201 | 202 | // Prevent visiting again 203 | if ((node as any).hasBeenVisited) { 204 | path.skip(); 205 | return; 206 | } 207 | 208 | if (!node.id) return; 209 | const props = propTypes.body.find((prop) => prop.name === node.id!.name); 210 | if (!props) return; 211 | 212 | // Prevent visiting again 213 | (node as any).hasBeenVisited = true; 214 | path.skip(); 215 | 216 | const prop = node.params[0]; 217 | injectPropTypes({ 218 | nodeName: node.id.name, 219 | usedProps: 220 | babelTypes.isIdentifier(prop) || babelTypes.isObjectPattern(prop) 221 | ? getUsedProps(path, prop) 222 | : [], 223 | path, 224 | props, 225 | }); 226 | }, 227 | VariableDeclarator(path) { 228 | const { node } = path; 229 | 230 | // Prevent visiting again 231 | if ((node as any).hasBeenVisited) { 232 | path.skip(); 233 | return; 234 | } 235 | 236 | if (!babelTypes.isIdentifier(node.id)) return; 237 | const nodeName = node.id.name; 238 | 239 | const props = propTypes.body.find((prop) => prop.name === nodeName); 240 | if (!props) return; 241 | 242 | if ( 243 | babelTypes.isArrowFunctionExpression(node.init) || 244 | babelTypes.isFunctionExpression(node.init) 245 | ) { 246 | getFromProp(node.init.params[0]); 247 | } 248 | // x = react.memo(props =>
) 249 | else if (babelTypes.isCallExpression(node.init)) { 250 | const arg = node.init.arguments[0]; 251 | if (babelTypes.isArrowFunctionExpression(arg) || babelTypes.isFunctionExpression(arg)) { 252 | getFromProp(arg.params[0]); 253 | } 254 | } 255 | 256 | function getFromProp(prop: babelTypes.Node) { 257 | // Prevent visiting again 258 | (node as any).hasBeenVisited = true; 259 | path.skip(); 260 | 261 | injectPropTypes({ 262 | path: path.parentPath, 263 | usedProps: 264 | babelTypes.isIdentifier(prop) || babelTypes.isObjectPattern(prop) 265 | ? getUsedProps(path, prop) 266 | : [], 267 | props: props!, 268 | nodeName, 269 | }); 270 | } 271 | }, 272 | ClassDeclaration(path) { 273 | const { node } = path; 274 | 275 | // Prevent visiting again 276 | if ((node as any).hasBeenVisited) { 277 | path.skip(); 278 | return; 279 | } 280 | 281 | if (!babelTypes.isIdentifier(node.id)) return; 282 | const nodeName = node.id.name; 283 | 284 | const props = propTypes.body.find((prop) => prop.name === nodeName); 285 | if (!props) return; 286 | 287 | // Prevent visiting again 288 | (node as any).hasBeenVisited = true; 289 | path.skip(); 290 | 291 | injectPropTypes({ 292 | nodeName, 293 | usedProps: getUsedProps(path, undefined), 294 | path, 295 | props, 296 | }); 297 | }, 298 | }, 299 | }; 300 | 301 | function injectPropTypes(options: { 302 | path: babel.NodePath; 303 | usedProps: string[]; 304 | props: t.ComponentNode; 305 | nodeName: string; 306 | }) { 307 | const { path, props, usedProps, nodeName } = options; 308 | 309 | const source = generate(props, { 310 | ...otherOptions, 311 | importedName: importName, 312 | previousPropTypesSource, 313 | reconcilePropTypes, 314 | shouldInclude: (prop) => shouldInclude({ component: props, prop, usedProps }), 315 | }); 316 | 317 | if (source.length === 0) { 318 | return; 319 | } 320 | 321 | needImport = true; 322 | 323 | const placeholder = `const a${uuid().replace(/\-/g, '_')} = null;`; 324 | 325 | mapOfPropTypes.set(placeholder, source); 326 | 327 | if (removeExistingPropTypes && originalPropTypesPath !== null) { 328 | originalPropTypesPath.replaceWith(babel.template.ast(placeholder) as any); 329 | } else if (babelTypes.isExportNamedDeclaration(path.parent)) { 330 | path.insertAfter(babel.template.ast(`export { ${nodeName} };`)); 331 | path.insertAfter(babel.template.ast(placeholder)); 332 | path.parentPath.replaceWith(path.node); 333 | } else if (babelTypes.isExportDefaultDeclaration(path.parent)) { 334 | path.insertAfter(babel.template.ast(`export default ${nodeName};`)); 335 | path.insertAfter(babel.template.ast(placeholder)); 336 | path.parentPath.replaceWith(path.node); 337 | } else { 338 | path.insertAfter(babel.template.ast(placeholder)); 339 | } 340 | } 341 | } 342 | 343 | /** 344 | * Gets used props from path 345 | * @param rootPath The path to search for uses of rootNode 346 | * @param rootNode The node to start the search, if undefined searches for `this.props` 347 | */ 348 | function getUsedProps( 349 | rootPath: babel.NodePath, 350 | rootNode: babelTypes.ObjectPattern | babelTypes.Identifier | undefined 351 | ) { 352 | const usedProps: string[] = []; 353 | getUsedPropsInternal(rootNode); 354 | return usedProps; 355 | 356 | function getUsedPropsInternal( 357 | node: babelTypes.ObjectPattern | babelTypes.Identifier | undefined 358 | ) { 359 | if (node && babelTypes.isObjectPattern(node)) { 360 | node.properties.forEach((x) => { 361 | if (babelTypes.isObjectProperty(x)) { 362 | if (babelTypes.isStringLiteral(x.key)) { 363 | usedProps.push(x.key.value); 364 | } else if (babelTypes.isIdentifier(x.key)) { 365 | usedProps.push(x.key.name); 366 | } else { 367 | console.warn( 368 | 'Possibly used prop missed because object property key was not an Identifier or StringLiteral.' 369 | ); 370 | } 371 | } else if (babelTypes.isIdentifier(x.argument)) { 372 | getUsedPropsInternal(x.argument); 373 | } 374 | }); 375 | } else { 376 | rootPath.traverse({ 377 | VariableDeclarator(path) { 378 | const init = path.node.init; 379 | if ( 380 | (node 381 | ? babelTypes.isIdentifier(init, { name: node.name }) 382 | : babelTypes.isMemberExpression(init) && 383 | babelTypes.isThisExpression(init.object) && 384 | babelTypes.isIdentifier(init.property, { name: 'props' })) && 385 | babelTypes.isObjectPattern(path.node.id) 386 | ) { 387 | getUsedPropsInternal(path.node.id); 388 | } 389 | }, 390 | MemberExpression(path) { 391 | if ( 392 | (node 393 | ? babelTypes.isIdentifier(path.node.object, { name: node.name }) 394 | : babelTypes.isMemberExpression(path.node.object) && 395 | babelTypes.isMemberExpression(path.node.object.object) && 396 | babelTypes.isThisExpression(path.node.object.object.object) && 397 | babelTypes.isIdentifier(path.node.object.object.property, { name: 'props' })) && 398 | babelTypes.isIdentifier(path.node.property) 399 | ) { 400 | usedProps.push(path.node.property.name); 401 | } 402 | }, 403 | }); 404 | } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import * as t from './types'; 3 | import * as doctrine from 'doctrine'; 4 | 5 | /** 6 | * Options that specify how the parser should act 7 | */ 8 | export interface ParserOptions { 9 | /** 10 | * Called before a PropType is added to a component/object 11 | * @return true to include the PropType, false to skip it, or undefined to 12 | * use the default behaviour 13 | * @default name !== 'ref' 14 | */ 15 | shouldInclude: (data: { name: string; depth: number }) => boolean | undefined; 16 | /** 17 | * Called before the shape of an object is resolved 18 | * @return true to resolve the shape of the object, false to just use a object, or undefined to 19 | * use the default behaviour 20 | * @default propertyCount <= 50 && depth <= 3 21 | */ 22 | shouldResolveObject: (data: { 23 | name: string; 24 | propertyCount: number; 25 | depth: number; 26 | }) => boolean | undefined; 27 | /** 28 | * Control if const declarations should be checked 29 | * @default false 30 | * @example declare const Component: React.ComponentType; 31 | */ 32 | checkDeclarations?: boolean; 33 | } 34 | 35 | /** 36 | * A wrapper for `ts.createProgram` 37 | * @param files The files to later be parsed with `parseFromProgram` 38 | * @param options The options to pass to the compiler 39 | */ 40 | export function createProgram(files: string[], options: ts.CompilerOptions) { 41 | return ts.createProgram(files, options); 42 | } 43 | 44 | /** 45 | * Creates a program, parses the specified file and returns the PropTypes as an AST, if you need to parse more than one file 46 | * use `createProgram` and `parseFromProgram` for better performance 47 | * @param filePath The file to parse 48 | * @param options The options from `loadConfig` 49 | * @param parserOptions Options that specify how the parser should act 50 | */ 51 | export function parseFile( 52 | filePath: string, 53 | options: ts.CompilerOptions, 54 | parserOptions: Partial = {} 55 | ) { 56 | const program = ts.createProgram([filePath], options); 57 | return parseFromProgram(filePath, program, parserOptions); 58 | } 59 | 60 | /** 61 | * Parses the specified file and returns the PropTypes as an AST 62 | * @param filePath The file to get the PropTypes from 63 | * @param program The program object returned by `createProgram` 64 | * @param parserOptions Options that specify how the parser should act 65 | */ 66 | export function parseFromProgram( 67 | filePath: string, 68 | program: ts.Program, 69 | parserOptions: Partial = {} 70 | ) { 71 | const { checkDeclarations = false } = parserOptions; 72 | 73 | const shouldInclude: ParserOptions['shouldInclude'] = (data) => { 74 | if (parserOptions.shouldInclude) { 75 | const result = parserOptions.shouldInclude(data); 76 | if (result !== undefined) { 77 | return result; 78 | } 79 | } 80 | 81 | return data.name !== 'ref'; 82 | }; 83 | 84 | const shouldResolveObject: ParserOptions['shouldResolveObject'] = (data) => { 85 | if (parserOptions.shouldResolveObject) { 86 | const result = parserOptions.shouldResolveObject(data); 87 | if (result !== undefined) { 88 | return result; 89 | } 90 | } 91 | 92 | return data.propertyCount <= 50 && data.depth <= 3; 93 | }; 94 | 95 | const checker = program.getTypeChecker(); 96 | const sourceFile = program.getSourceFile(filePath); 97 | 98 | const programNode = t.programNode(); 99 | const reactImports: string[] = []; 100 | 101 | if (sourceFile) { 102 | ts.forEachChild(sourceFile, visitImports); 103 | ts.forEachChild(sourceFile, visit); 104 | } else { 105 | throw new Error(`Program doesn't contain file "${filePath}"`); 106 | } 107 | 108 | return programNode; 109 | 110 | function visitImports(node: ts.Node) { 111 | if ( 112 | ts.isImportDeclaration(node) && 113 | ts.isStringLiteral(node.moduleSpecifier) && 114 | node.moduleSpecifier.text === 'react' && 115 | node.importClause 116 | ) { 117 | const imports = ['Component', 'PureComponent', 'memo', 'forwardRef']; 118 | 119 | // import x from 'react' 120 | if (node.importClause.name) { 121 | const nameText = node.importClause.name.text; 122 | reactImports.push(...imports.map((x) => `${nameText}.${x}`)); 123 | } 124 | 125 | // import {x, y as z} from 'react' 126 | const bindings = node.importClause.namedBindings; 127 | if (bindings) { 128 | if (ts.isNamedImports(bindings)) { 129 | bindings.elements.forEach((spec) => { 130 | const nameIdentifier = spec.propertyName || spec.name; 131 | const nameText = nameIdentifier.getText(); 132 | if (imports.includes(nameText)) { 133 | reactImports.push(spec.name.getText()); 134 | } 135 | }); 136 | } 137 | // import * as x from 'react' 138 | else { 139 | const nameText = bindings.name.text; 140 | reactImports.push(...imports.map((x) => `${nameText}.${x}`)); 141 | } 142 | } 143 | } 144 | } 145 | 146 | function visit(node: ts.Node) { 147 | // function x(props: type) { return
} 148 | if (ts.isFunctionDeclaration(node) && node.name && node.parameters.length === 1) { 149 | parseFunctionComponent(node); 150 | } 151 | // const x = ... 152 | else if (ts.isVariableStatement(node)) { 153 | ts.forEachChild(node.declarationList, (variableNode) => { 154 | // x = (props: type) => { return
} 155 | // x = function(props: type) { return
} 156 | // x = function y(props: type) { return
} 157 | // x = react.memo((props:type) { return
}) 158 | 159 | if (ts.isVariableDeclaration(variableNode) && variableNode.name) { 160 | const type = checker.getTypeAtLocation(variableNode.name); 161 | if (!variableNode.initializer) { 162 | if ( 163 | checkDeclarations && 164 | type.aliasSymbol && 165 | type.aliasTypeArguments && 166 | checker.getFullyQualifiedName(type.aliasSymbol) === 'React.ComponentType' 167 | ) { 168 | parsePropsType( 169 | variableNode.name.getText(), 170 | type.aliasTypeArguments[0], 171 | node.getSourceFile() 172 | ); 173 | } else if (checkDeclarations) { 174 | parseFunctionComponent(variableNode); 175 | } 176 | } else if ( 177 | (ts.isArrowFunction(variableNode.initializer) || 178 | ts.isFunctionExpression(variableNode.initializer)) && 179 | variableNode.initializer.parameters.length === 1 180 | ) { 181 | parseFunctionComponent(variableNode); 182 | } 183 | // x = react.memo((props:type) { return
}) 184 | else if ( 185 | ts.isCallExpression(variableNode.initializer) && 186 | variableNode.initializer.arguments.length > 0 187 | ) { 188 | const callString = variableNode.initializer.expression.getText(); 189 | const arg = variableNode.initializer.arguments[0]; 190 | if ( 191 | reactImports.includes(callString) && 192 | (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) && 193 | arg.parameters.length > 0 194 | ) { 195 | const propsType = checker.getTypeAtLocation(arg.parameters[0]); 196 | if (propsType) { 197 | parsePropsType(variableNode.name.getText(), propsType, node.getSourceFile()); 198 | } 199 | } 200 | } 201 | } 202 | }); 203 | } else if ( 204 | ts.isClassDeclaration(node) && 205 | node.name && 206 | node.heritageClauses && 207 | node.heritageClauses.length === 1 208 | ) { 209 | const heritage = node.heritageClauses[0]; 210 | if (heritage.types.length !== 1) return; 211 | 212 | const arg = heritage.types[0]; 213 | if (!arg.typeArguments) return; 214 | 215 | if (reactImports.includes(arg.expression.getText())) { 216 | parsePropsType( 217 | node.name.getText(), 218 | checker.getTypeAtLocation(arg.typeArguments[0]), 219 | node.getSourceFile() 220 | ); 221 | } 222 | } 223 | } 224 | 225 | function isTypeJSXElementLike(type: ts.Type): boolean { 226 | if (type.isUnion()) { 227 | return type.types.every( 228 | (subType) => subType.flags & ts.TypeFlags.Null || isTypeJSXElementLike(subType) 229 | ); 230 | } else if (type.symbol) { 231 | const name = checker.getFullyQualifiedName(type.symbol); 232 | return name === 'global.JSX.Element' || name === 'React.ReactElement'; 233 | } 234 | 235 | return false; 236 | } 237 | 238 | function parseFunctionComponent(node: ts.VariableDeclaration | ts.FunctionDeclaration) { 239 | if (!node.name) { 240 | return; 241 | } 242 | 243 | const symbol = checker.getSymbolAtLocation(node.name); 244 | if (!symbol) { 245 | return; 246 | } 247 | const componentName = node.name.getText(); 248 | 249 | const type = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); 250 | type.getCallSignatures().forEach((signature) => { 251 | if (!isTypeJSXElementLike(signature.getReturnType())) { 252 | return; 253 | } 254 | 255 | const propsType = checker.getTypeOfSymbolAtLocation( 256 | signature.parameters[0], 257 | signature.parameters[0].valueDeclaration 258 | ); 259 | 260 | parsePropsType(componentName, propsType, node.getSourceFile()); 261 | }); 262 | 263 | // squash props 264 | // { variant: 'a', href: string } & { variant: 'b' } 265 | // to 266 | // { variant: 'a' | 'b', href?: string } 267 | const props: Record = {}; 268 | const usedPropsPerSignature: Set[] = []; 269 | programNode.body = programNode.body.filter((node) => { 270 | if (node.name === componentName) { 271 | const usedProps: Set = new Set(); 272 | // squash props 273 | node.types.forEach((typeNode) => { 274 | usedProps.add(typeNode.name); 275 | 276 | let { [typeNode.name]: currentTypeNode } = props; 277 | if (currentTypeNode === undefined) { 278 | currentTypeNode = typeNode; 279 | } else if (currentTypeNode.$$id !== typeNode.$$id) { 280 | currentTypeNode = t.propTypeNode( 281 | currentTypeNode.name, 282 | currentTypeNode.jsDoc, 283 | t.unionNode([currentTypeNode.propType, typeNode.propType]), 284 | new Set(Array.from(currentTypeNode.filenames).concat(Array.from(typeNode.filenames))), 285 | undefined 286 | ); 287 | } 288 | 289 | props[typeNode.name] = currentTypeNode; 290 | }); 291 | 292 | usedPropsPerSignature.push(usedProps); 293 | 294 | // delete each signature, we'll add it later unionized 295 | return false; 296 | } 297 | return true; 298 | }); 299 | 300 | programNode.body.push( 301 | t.componentNode( 302 | componentName, 303 | Object.entries(props).map(([name, propType]) => { 304 | const onlyUsedInSomeSignatures = usedPropsPerSignature.some((props) => !props.has(name)); 305 | if (onlyUsedInSomeSignatures) { 306 | // mark as optional 307 | return { 308 | ...propType, 309 | propType: t.unionNode([propType.propType, t.undefinedNode()]), 310 | }; 311 | } 312 | return propType; 313 | }), 314 | node.getSourceFile().fileName 315 | ) 316 | ); 317 | } 318 | 319 | function parsePropsType(name: string, type: ts.Type, sourceFile: ts.SourceFile | undefined) { 320 | const properties = type 321 | .getProperties() 322 | .filter((symbol) => shouldInclude({ name: symbol.getName(), depth: 1 })); 323 | if (properties.length === 0) { 324 | return; 325 | } 326 | 327 | const propsFilename = sourceFile !== undefined ? sourceFile.fileName : undefined; 328 | 329 | programNode.body.push( 330 | t.componentNode( 331 | name, 332 | properties.map((x) => checkSymbol(x, new Set([(type as any).id]))), 333 | propsFilename 334 | ) 335 | ); 336 | } 337 | 338 | function checkSymbol(symbol: ts.Symbol, typeStack: Set): t.PropTypeNode { 339 | const declarations = symbol.getDeclarations(); 340 | const declaration = declarations && declarations[0]; 341 | 342 | const symbolFilenames = getSymbolFileNames(symbol); 343 | 344 | // TypeChecker keeps the name for 345 | // { a: React.ElementType, b: React.ReactElement | boolean } 346 | // but not 347 | // { a?: React.ElementType, b: React.ReactElement } 348 | // get around this by not using the TypeChecker 349 | if ( 350 | declaration && 351 | ts.isPropertySignature(declaration) && 352 | declaration.type && 353 | ts.isTypeReferenceNode(declaration.type) 354 | ) { 355 | const name = declaration.type.typeName.getText(); 356 | if ( 357 | name === 'React.ElementType' || 358 | name === 'React.ComponentType' || 359 | name === 'React.ReactElement' || 360 | name === 'React.MemoExoticComponent' || 361 | name === 'React.Component' 362 | ) { 363 | const elementNode = t.elementNode( 364 | name === 'React.ReactElement' ? 'element' : 'elementType' 365 | ); 366 | 367 | return t.propTypeNode( 368 | symbol.getName(), 369 | getDocumentation(symbol), 370 | declaration.questionToken ? t.unionNode([t.undefinedNode(), elementNode]) : elementNode, 371 | symbolFilenames, 372 | (symbol as any).id 373 | ); 374 | } 375 | } 376 | 377 | const symbolType = declaration 378 | ? // The proptypes aren't detailed enough that we need all the different combinations 379 | // so we just pick the first and ignore the rest 380 | checker.getTypeOfSymbolAtLocation(symbol, declaration) 381 | : // The properties of Record<..., ...> don't have a declaration, but the symbol has a type property 382 | ((symbol as any).type as ts.Type); 383 | // get `React.ElementType` from `C extends React.ElementType` 384 | const declaredType = 385 | declaration !== undefined ? checker.getTypeAtLocation(declaration) : undefined; 386 | const baseConstraintOfType = 387 | declaredType !== undefined ? checker.getBaseConstraintOfType(declaredType) : undefined; 388 | const type = 389 | baseConstraintOfType !== undefined && baseConstraintOfType !== declaredType 390 | ? baseConstraintOfType 391 | : symbolType; 392 | 393 | if (!type) { 394 | throw new Error('No types found'); 395 | } 396 | 397 | // Typechecker only gives the type "any" if it's present in a union 398 | // This means the type of "a" in {a?:any} isn't "any | undefined" 399 | // So instead we check for the questionmark to detect optional types 400 | let parsedType: t.Node | undefined = undefined; 401 | if ( 402 | (type.flags & ts.TypeFlags.Any || type.flags & ts.TypeFlags.Unknown) && 403 | declaration && 404 | ts.isPropertySignature(declaration) 405 | ) { 406 | parsedType = declaration.questionToken 407 | ? t.unionNode([t.undefinedNode(), t.anyNode()]) 408 | : t.anyNode(); 409 | } else { 410 | parsedType = checkType(type, typeStack, symbol.getName()); 411 | } 412 | 413 | return t.propTypeNode( 414 | symbol.getName(), 415 | getDocumentation(symbol), 416 | parsedType, 417 | symbolFilenames, 418 | (symbol as any).id 419 | ); 420 | } 421 | 422 | function checkType(type: ts.Type, typeStack: Set, name: string): t.Node { 423 | // If the typeStack contains type.id we're dealing with an object that references itself. 424 | // To prevent getting stuck in an infinite loop we just set it to an objectNode 425 | if (typeStack.has((type as any).id)) { 426 | return t.objectNode(); 427 | } 428 | 429 | { 430 | const typeNode = type as any; 431 | 432 | const symbol = typeNode.aliasSymbol ? typeNode.aliasSymbol : typeNode.symbol; 433 | const typeName = symbol ? checker.getFullyQualifiedName(symbol) : null; 434 | switch (typeName) { 435 | case 'global.JSX.Element': 436 | case 'React.ReactElement': { 437 | return t.elementNode('element'); 438 | } 439 | case 'React.ElementType': { 440 | return t.elementNode('elementType'); 441 | } 442 | case 'React.ReactNode': { 443 | return t.unionNode([t.elementNode('node'), t.undefinedNode()]); 444 | } 445 | case 'Date': 446 | case 'React.Component': { 447 | return t.instanceOfNode(typeName); 448 | } 449 | case 'Element': 450 | case 'HTMLElement': { 451 | return t.DOMElementNode(); 452 | } 453 | } 454 | } 455 | 456 | // @ts-ignore - Private method 457 | if (checker.isArrayType(type)) { 458 | // @ts-ignore - Private method 459 | const arrayType: ts.Type = checker.getElementTypeOfArrayType(type); 460 | return t.arrayNode(checkType(arrayType, typeStack, name)); 461 | } 462 | 463 | if (type.isUnion()) { 464 | const node = t.unionNode(type.types.map((x) => checkType(x, typeStack, name))); 465 | 466 | return node.types.length === 1 ? node.types[0] : node; 467 | } 468 | 469 | if (type.flags & ts.TypeFlags.String) { 470 | return t.stringNode(); 471 | } 472 | 473 | if (type.flags & ts.TypeFlags.Number) { 474 | return t.numericNode(); 475 | } 476 | 477 | if (type.flags & ts.TypeFlags.Undefined) { 478 | return t.undefinedNode(); 479 | } 480 | 481 | if (type.flags & ts.TypeFlags.Any || type.flags & ts.TypeFlags.Unknown) { 482 | return t.anyNode(); 483 | } 484 | 485 | if (type.flags & ts.TypeFlags.Literal) { 486 | if (type.isLiteral()) { 487 | return t.literalNode( 488 | type.isStringLiteral() ? `"${type.value}"` : type.value, 489 | getDocumentation(type.symbol) 490 | ); 491 | } 492 | return t.literalNode(checker.typeToString(type)); 493 | } 494 | 495 | if (type.flags & ts.TypeFlags.Null) { 496 | return t.literalNode('null'); 497 | } 498 | 499 | if (type.getCallSignatures().length) { 500 | return t.functionNode(); 501 | } 502 | 503 | // Object-like type 504 | { 505 | const properties = type.getProperties(); 506 | if (properties.length) { 507 | if ( 508 | shouldResolveObject({ name, propertyCount: properties.length, depth: typeStack.size }) 509 | ) { 510 | const filtered = properties.filter((symbol) => 511 | shouldInclude({ name: symbol.getName(), depth: typeStack.size + 1 }) 512 | ); 513 | if (filtered.length > 0) { 514 | return t.interfaceNode( 515 | filtered.map((x) => 516 | checkSymbol(x, new Set([...typeStack.values(), (type as any).id])) 517 | ) 518 | ); 519 | } 520 | } 521 | 522 | return t.objectNode(); 523 | } 524 | } 525 | 526 | // Object without properties or object keyword 527 | if ( 528 | type.flags & ts.TypeFlags.Object || 529 | (type.flags & ts.TypeFlags.NonPrimitive && checker.typeToString(type) === 'object') 530 | ) { 531 | return t.objectNode(); 532 | } 533 | 534 | console.warn( 535 | `Unable to handle node of type "ts.TypeFlags.${ts.TypeFlags[type.flags]}", using any` 536 | ); 537 | return t.anyNode(); 538 | } 539 | 540 | function getDocumentation(symbol?: ts.Symbol): string | undefined { 541 | if (!symbol) { 542 | return undefined; 543 | } 544 | 545 | const decl = symbol.getDeclarations(); 546 | if (decl) { 547 | // @ts-ignore - Private method 548 | const comments = ts.getJSDocCommentsAndTags(decl[0]) as any[]; 549 | if (comments && comments.length === 1) { 550 | const commentNode = comments[0]; 551 | if (ts.isJSDoc(commentNode)) { 552 | return doctrine.unwrapComment(commentNode.getText()).trim(); 553 | } 554 | } 555 | } 556 | 557 | const comment = ts.displayPartsToString(symbol.getDocumentationComment(checker)); 558 | return comment ? comment : undefined; 559 | } 560 | 561 | function getSymbolFileNames(symbol: ts.Symbol): Set { 562 | const declarations = symbol.getDeclarations() || []; 563 | 564 | return new Set(declarations.map((declaration) => declaration.getSourceFile().fileName)); 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nodes/baseNodes'; 2 | export * from './nodes/program'; 3 | export * from './nodes/component'; 4 | export * from './nodes/proptype'; 5 | 6 | export * from './props/function'; 7 | export * from './props/interface'; 8 | export * from './props/string'; 9 | export * from './props/union'; 10 | export * from './props/undefined'; 11 | export * from './props/boolean'; 12 | export * from './props/numeric'; 13 | export * from './props/literal'; 14 | export * from './props/any'; 15 | export * from './props/object'; 16 | export * from './props/array'; 17 | export * from './props/element'; 18 | export * from './props/instanceOf'; 19 | export * from './props/DOMElement'; 20 | -------------------------------------------------------------------------------- /src/types/nodes/baseNodes.ts: -------------------------------------------------------------------------------- 1 | import { PropTypeNode } from './proptype'; 2 | 3 | export interface Node { 4 | type: string; 5 | } 6 | 7 | export interface DefinitionHolder extends Node { 8 | types: PropTypeNode[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/types/nodes/component.ts: -------------------------------------------------------------------------------- 1 | import { Node, DefinitionHolder } from './baseNodes'; 2 | import { PropTypeNode } from './proptype'; 3 | 4 | const typeString = 'ComponentNode'; 5 | 6 | export interface ComponentNode extends DefinitionHolder { 7 | name: string; 8 | propsFilename?: string; 9 | } 10 | 11 | export function componentNode( 12 | name: string, 13 | types: PropTypeNode[], 14 | propsFilename: string | undefined 15 | ): ComponentNode { 16 | return { 17 | type: typeString, 18 | name: name, 19 | types: types || [], 20 | propsFilename, 21 | }; 22 | } 23 | 24 | export function isComponentNode(node: Node): node is ComponentNode { 25 | return node.type === typeString; 26 | } 27 | -------------------------------------------------------------------------------- /src/types/nodes/program.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './baseNodes'; 2 | import { ComponentNode } from './component'; 3 | 4 | const typeString = 'ProgramNode'; 5 | 6 | export interface ProgramNode extends Node { 7 | body: ComponentNode[]; 8 | } 9 | 10 | export function programNode(body?: ComponentNode[]): ProgramNode { 11 | return { 12 | type: typeString, 13 | body: body || [], 14 | }; 15 | } 16 | 17 | export function isProgramNode(node: Node): node is ProgramNode { 18 | return node.type === typeString; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/nodes/proptype.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './baseNodes'; 2 | 3 | const typeString = 'PropTypeNode'; 4 | 5 | export interface PropTypeNode extends Node { 6 | name: string; 7 | jsDoc?: string; 8 | propType: Node; 9 | filenames: Set; 10 | /** 11 | * @internal 12 | */ 13 | $$id: number | undefined; 14 | } 15 | 16 | export function propTypeNode( 17 | name: string, 18 | jsDoc: string | undefined, 19 | propType: Node, 20 | filenames: Set, 21 | id: number | undefined 22 | ): PropTypeNode { 23 | return { 24 | type: typeString, 25 | name, 26 | jsDoc, 27 | propType, 28 | filenames, 29 | $$id: id, 30 | }; 31 | } 32 | 33 | export function isPropTypeNode(node: Node): node is PropTypeNode { 34 | return node.type === typeString; 35 | } 36 | -------------------------------------------------------------------------------- /src/types/props/DOMElement.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'DOMElementNode'; 4 | 5 | interface DOMElementNode extends Node { 6 | optional?: boolean; 7 | } 8 | 9 | export function DOMElementNode(optional?: boolean): DOMElementNode { 10 | return { 11 | type: typeString, 12 | optional, 13 | }; 14 | } 15 | 16 | export function isDOMElementNode(node: Node): node is DOMElementNode { 17 | return node.type === typeString; 18 | } 19 | -------------------------------------------------------------------------------- /src/types/props/any.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'AnyNode'; 4 | 5 | export function anyNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isAnyNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/array.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'ArrayNode'; 4 | 5 | export interface ArrayNode extends Node { 6 | arrayType: Node; 7 | } 8 | 9 | export function arrayNode(arrayType: Node): ArrayNode { 10 | return { 11 | type: typeString, 12 | arrayType, 13 | }; 14 | } 15 | 16 | export function isArrayNode(node: Node): node is ArrayNode { 17 | return node.type === typeString; 18 | } 19 | -------------------------------------------------------------------------------- /src/types/props/boolean.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'BooleanNode'; 4 | 5 | export function booleanNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isBooleanNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/element.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'ElementNode'; 4 | type ElementType = 'element' | 'node' | 'elementType'; 5 | 6 | interface ElementNode extends Node { 7 | elementType: ElementType; 8 | } 9 | 10 | export function elementNode(elementType: ElementType): ElementNode { 11 | return { 12 | type: typeString, 13 | elementType, 14 | }; 15 | } 16 | 17 | export function isElementNode(node: Node): node is ElementNode { 18 | return node.type === typeString; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/props/function.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'FunctionNode'; 4 | 5 | export function functionNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isFunctionNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/instanceOf.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'InstanceOfNode'; 4 | 5 | export interface InstanceOfNode extends Node { 6 | instance: string; 7 | } 8 | 9 | export function instanceOfNode(instance: string): InstanceOfNode { 10 | return { 11 | type: typeString, 12 | instance, 13 | }; 14 | } 15 | 16 | export function isInstanceOfNode(node: Node): node is InstanceOfNode { 17 | return node.type === typeString; 18 | } 19 | -------------------------------------------------------------------------------- /src/types/props/interface.ts: -------------------------------------------------------------------------------- 1 | import { Node, DefinitionHolder } from '../nodes/baseNodes'; 2 | import { PropTypeNode } from '../nodes/proptype'; 3 | 4 | const typeString = 'InterfaceNode'; 5 | 6 | export interface InterfaceNode extends DefinitionHolder {} 7 | 8 | export function interfaceNode(types?: PropTypeNode[]): InterfaceNode { 9 | return { 10 | type: typeString, 11 | types: types || [], 12 | }; 13 | } 14 | 15 | export function isInterfaceNode(node: Node): node is InterfaceNode { 16 | return node.type === typeString; 17 | } 18 | -------------------------------------------------------------------------------- /src/types/props/literal.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'LiteralNode'; 4 | 5 | export interface LiteralNode extends Node { 6 | value: unknown; 7 | jsDoc?: string; 8 | } 9 | 10 | export function literalNode(value: unknown, jsDoc?: string): LiteralNode { 11 | return { 12 | type: typeString, 13 | value, 14 | jsDoc, 15 | }; 16 | } 17 | 18 | export function isLiteralNode(node: Node): node is LiteralNode { 19 | return node.type === typeString; 20 | } 21 | -------------------------------------------------------------------------------- /src/types/props/numeric.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'NumericNode'; 4 | 5 | export function numericNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isNumericNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/object.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'ObjectNode'; 4 | 5 | export function objectNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isObjectNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/string.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'StringNode'; 4 | 5 | export function stringNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isStringNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/undefined.ts: -------------------------------------------------------------------------------- 1 | import { Node } from '../nodes/baseNodes'; 2 | 3 | const typeString = 'UndefinedNode'; 4 | 5 | export function undefinedNode(): Node { 6 | return { 7 | type: typeString, 8 | }; 9 | } 10 | 11 | export function isUndefinedNode(node: Node) { 12 | return node.type === typeString; 13 | } 14 | -------------------------------------------------------------------------------- /src/types/props/union.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import * as t from '../../types'; 3 | import { Node } from '../nodes/baseNodes'; 4 | 5 | const typeString = 'UnionNode'; 6 | 7 | export interface UnionNode extends Node { 8 | types: Node[]; 9 | } 10 | 11 | export function unionNode(types: Node[]): UnionNode { 12 | const flatTypes: Node[] = []; 13 | 14 | flattenTypes(types); 15 | 16 | function flattenTypes(nodes: Node[]) { 17 | nodes.forEach((x) => { 18 | if (isUnionNode(x)) { 19 | flattenTypes(x.types); 20 | } else { 21 | flatTypes.push(x); 22 | } 23 | }); 24 | } 25 | 26 | return uniqueUnionTypes({ 27 | type: typeString, 28 | types: flatTypes, 29 | }); 30 | } 31 | 32 | export function isUnionNode(node: Node): node is UnionNode { 33 | return node.type === typeString; 34 | } 35 | 36 | export function uniqueUnionTypes(node: UnionNode): UnionNode { 37 | return { 38 | type: node.type, 39 | types: _.uniqBy(node.types, (x) => { 40 | if (t.isLiteralNode(x)) { 41 | return x.value; 42 | } 43 | 44 | if (t.isInstanceOfNode(x)) { 45 | return `${x.type}.${x.instance}`; 46 | } 47 | 48 | return x.type; 49 | }), 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /test/boolean-values/optional/input.d.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | foo?: boolean; 3 | bar?: true; 4 | baz?: false; 5 | }; 6 | 7 | export default function Foo(props: Props): JSX.Element; 8 | -------------------------------------------------------------------------------- /test/boolean-values/optional/output.js: -------------------------------------------------------------------------------- 1 | Foo.propTypes = { 2 | bar: PropTypes.oneOf([true]), 3 | baz: PropTypes.oneOf([false]), 4 | foo: PropTypes.bool, 5 | }; 6 | -------------------------------------------------------------------------------- /test/boolean-values/optional/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "foo", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [ 14 | { "type": "UndefinedNode" }, 15 | { "type": "LiteralNode", "value": "false" }, 16 | { "type": "LiteralNode", "value": "true" } 17 | ] 18 | } 19 | }, 20 | { 21 | "type": "PropTypeNode", 22 | "name": "bar", 23 | "propType": { 24 | "type": "UnionNode", 25 | "types": [{ "type": "UndefinedNode" }, { "type": "LiteralNode", "value": "true" }] 26 | } 27 | }, 28 | { 29 | "type": "PropTypeNode", 30 | "name": "baz", 31 | "propType": { 32 | "type": "UnionNode", 33 | "types": [{ "type": "UndefinedNode" }, { "type": "LiteralNode", "value": "false" }] 34 | } 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /test/boolean-values/required/input.d.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | foo: boolean; 3 | bar: true; 4 | baz: false; 5 | }; 6 | 7 | export default function Foo(props: Props): JSX.Element; 8 | -------------------------------------------------------------------------------- /test/boolean-values/required/output.js: -------------------------------------------------------------------------------- 1 | Foo.propTypes = { 2 | bar: PropTypes.oneOf([true]).isRequired, 3 | baz: PropTypes.oneOf([false]).isRequired, 4 | foo: PropTypes.bool.isRequired, 5 | }; 6 | -------------------------------------------------------------------------------- /test/boolean-values/required/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "foo", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [ 14 | { "type": "LiteralNode", "value": "false" }, 15 | { "type": "LiteralNode", "value": "true" } 16 | ] 17 | } 18 | }, 19 | { 20 | "type": "PropTypeNode", 21 | "name": "bar", 22 | "propType": { "type": "LiteralNode", "value": "true" } 23 | }, 24 | { 25 | "type": "PropTypeNode", 26 | "name": "baz", 27 | "propType": { "type": "LiteralNode", "value": "false" } 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/code-order/input.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | value: unknown; 5 | } 6 | 7 | export default function Component(props: Props): JSX.Element; 8 | -------------------------------------------------------------------------------- /test/code-order/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function Component(props) { 5 | const { value } = props; 6 | return
{value}
; 7 | } 8 | 9 | const someValidator = () => new Error(); 10 | 11 | Component.propTypes = { 12 | value: PropTypes.any, 13 | }; 14 | 15 | export default Component; 16 | -------------------------------------------------------------------------------- /test/code-order/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../types'; 2 | 3 | const options: TestOptions = { 4 | injector: { 5 | removeExistingPropTypes: true, 6 | }, 7 | }; 8 | 9 | export default options; 10 | -------------------------------------------------------------------------------- /test/code-order/output.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function Component(props) { 5 | const { value } = props; 6 | return
{value}
; 7 | } 8 | 9 | const someValidator = () => new Error(); 10 | 11 | Component.propTypes = { 12 | value: PropTypes.any.isRequired, 13 | }; 14 | 15 | export default Component; 16 | -------------------------------------------------------------------------------- /test/code-order/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Component", 7 | "types": [{ "type": "PropTypeNode", "name": "value", "propType": { "type": "AnyNode" } }] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/date/input.d.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | createdAt: Date; 3 | removedAt?: Date; 4 | }; 5 | 6 | export default function Foo(props: Props): JSX.Element; 7 | -------------------------------------------------------------------------------- /test/date/output.js: -------------------------------------------------------------------------------- 1 | Foo.propTypes = { 2 | createdAt: PropTypes.instanceOf(Date).isRequired, 3 | removedAt: PropTypes.instanceOf(Date), 4 | }; 5 | -------------------------------------------------------------------------------- /test/date/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "createdAt", 11 | "propType": { "type": "InstanceOfNode", "instance": "Date" }, 12 | "filenames": {} 13 | }, 14 | { 15 | "type": "PropTypeNode", 16 | "name": "removedAt", 17 | "propType": { 18 | "type": "UnionNode", 19 | "types": [{ "type": "UndefinedNode" }, { "type": "InstanceOfNode", "instance": "Date" }] 20 | }, 21 | "filenames": {} 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/destructured-forward-ref/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface GridProps { 4 | spacing?: 'initial' | 1 | 2 | 3 | 4 | 5 | 'auto'; 5 | } 6 | 7 | export const Grid = React.forwardRef(({ spacing }: GridProps, ref) => { 8 | return
spacing: {spacing}
; 9 | }); 10 | -------------------------------------------------------------------------------- /test/destructured-forward-ref/output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | const Grid = React.forwardRef(({ spacing }, ref) => { 4 | return
spacing: {spacing}
; 5 | }); 6 | 7 | Grid.propTypes = { 8 | spacing: PropTypes.oneOf(['auto', 'initial', 1, 2, 3, 4, 5]), 9 | }; 10 | 11 | export { Grid }; 12 | -------------------------------------------------------------------------------- /test/destructured-forward-ref/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Grid", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "spacing", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [ 14 | { "type": "UndefinedNode" }, 15 | { "type": "LiteralNode", "value": "\"initial\"" }, 16 | { "type": "LiteralNode", "value": 1 }, 17 | { "type": "LiteralNode", "value": 2 }, 18 | { "type": "LiteralNode", "value": 3 }, 19 | { "type": "LiteralNode", "value": 4 }, 20 | { "type": "LiteralNode", "value": 5 }, 21 | { "type": "LiteralNode", "value": "\"auto\"" } 22 | ] 23 | }, 24 | "filenames": {} 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/destructured-props/input.tsx: -------------------------------------------------------------------------------- 1 | interface GridProps { 2 | spacing?: 'initial' | 1 | 2 | 3 | 4 | 5 | 'auto'; 3 | } 4 | 5 | export default function Grid({ spacing }: GridProps) { 6 | return
spacing: {spacing}
; 7 | } 8 | -------------------------------------------------------------------------------- /test/destructured-props/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | function Grid({ spacing }) { 3 | return
spacing: {spacing}
; 4 | } 5 | 6 | Grid.propTypes = { 7 | spacing: PropTypes.oneOf(['auto', 'initial', 1, 2, 3, 4, 5]), 8 | }; 9 | 10 | export default Grid; 11 | -------------------------------------------------------------------------------- /test/destructured-props/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Grid", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "spacing", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [ 14 | { "type": "UndefinedNode" }, 15 | { "type": "LiteralNode", "value": "\"initial\"" }, 16 | { "type": "LiteralNode", "value": 1 }, 17 | { "type": "LiteralNode", "value": 2 }, 18 | { "type": "LiteralNode", "value": 3 }, 19 | { "type": "LiteralNode", "value": 4 }, 20 | { "type": "LiteralNode", "value": 5 }, 21 | { "type": "LiteralNode", "value": "\"auto\"" } 22 | ] 23 | }, 24 | "filenames": {} 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/generator/html-elements/input.d.ts: -------------------------------------------------------------------------------- 1 | export function Foo(props: { 2 | element: Element; 3 | optional?: Element; 4 | htmlElement: HTMLElement; 5 | bothTypes: Element | HTMLElement; 6 | }): JSX.Element; 7 | -------------------------------------------------------------------------------- /test/generator/html-elements/output.js: -------------------------------------------------------------------------------- 1 | Foo.propTypes = { 2 | bothTypes: function (props, propName) { 3 | if (props[propName] == null) { 4 | return new Error("Prop '" + propName + "' is required but wasn't specified"); 5 | } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { 6 | return new Error("Expected prop '" + propName + "' to be of type Element"); 7 | } 8 | }, 9 | element: function (props, propName) { 10 | if (props[propName] == null) { 11 | return new Error("Prop '" + propName + "' is required but wasn't specified"); 12 | } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { 13 | return new Error("Expected prop '" + propName + "' to be of type Element"); 14 | } 15 | }, 16 | htmlElement: function (props, propName) { 17 | if (props[propName] == null) { 18 | return new Error("Prop '" + propName + "' is required but wasn't specified"); 19 | } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { 20 | return new Error("Expected prop '" + propName + "' to be of type Element"); 21 | } 22 | }, 23 | optional: function (props, propName) { 24 | if (props[propName] == null) { 25 | return null; 26 | } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { 27 | return new Error("Expected prop '" + propName + "' to be of type Element"); 28 | } 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /test/generator/html-elements/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { "type": "PropTypeNode", "name": "element", "propType": { "type": "DOMElementNode" } }, 9 | { 10 | "type": "PropTypeNode", 11 | "name": "optional", 12 | "propType": { 13 | "type": "UnionNode", 14 | "types": [{ "type": "UndefinedNode" }, { "type": "DOMElementNode" }] 15 | } 16 | }, 17 | { "type": "PropTypeNode", "name": "htmlElement", "propType": { "type": "DOMElementNode" } }, 18 | { "type": "PropTypeNode", "name": "bothTypes", "propType": { "type": "DOMElementNode" } } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | import glob from 'glob'; 5 | import prettier from 'prettier'; 6 | import * as ttp from '../src'; 7 | import { TestOptions } from './types'; 8 | 9 | const prettierConfig = prettier.resolveConfig.sync(path.join(__dirname, '../.prettierrc')); 10 | 11 | const testCases = glob.sync('**/input.{d.ts,ts,tsx}', { absolute: true, cwd: __dirname }); 12 | 13 | // Create program for all files to speed up tests 14 | const program = ttp.createProgram( 15 | testCases, 16 | ttp.loadConfig(path.resolve(__dirname, '../tsconfig.json')) 17 | ); 18 | 19 | for (const testCase of testCases) { 20 | const dirname = path.dirname(testCase); 21 | const testName = dirname.substr(__dirname.length + 1); 22 | const astPath = path.join(dirname, 'output.json'); 23 | const outputPath = path.join(dirname, 'output.js'); 24 | const optionsPath = path.join(dirname, 'options.ts'); 25 | const inputJS = path.join(dirname, 'input.js'); 26 | 27 | it(testName, () => { 28 | const options: TestOptions = fs.existsSync(optionsPath) ? require(optionsPath).default : {}; 29 | 30 | const ast = ttp.parseFromProgram(testCase, program, options.parser); 31 | 32 | //#region Check AST matches 33 | // propsFilename will be different depending on where the project is on disk 34 | // Manually check that it's correct and then delete it 35 | const newAST = ttp.programNode( 36 | ast.body.map((component) => { 37 | expect(component.propsFilename).toBe(testCase); 38 | return { ...component, propsFilename: undefined }; 39 | }) 40 | ); 41 | 42 | if (fs.existsSync(astPath)) { 43 | expect(newAST).toMatchObject(JSON.parse(fs.readFileSync(astPath, 'utf8'))); 44 | } else { 45 | fs.writeFileSync( 46 | astPath, 47 | prettier.format( 48 | JSON.stringify(newAST, (key, value) => { 49 | // These are TypeScript internals that change depending on the number of symbols created during test 50 | if (key === '$$id') { 51 | return undefined; 52 | } 53 | return value; 54 | }), 55 | { 56 | ...prettierConfig, 57 | filepath: astPath, 58 | } 59 | ) 60 | ); 61 | } 62 | //#endregion 63 | 64 | let inputSource = null; 65 | if (testCase.endsWith('.d.ts')) { 66 | try { 67 | inputSource = fs.readFileSync(inputJS, 'utf8'); 68 | } catch (error) {} 69 | } else { 70 | inputSource = ttp.ts.transpileModule(fs.readFileSync(testCase, 'utf8'), { 71 | compilerOptions: { 72 | target: ttp.ts.ScriptTarget.ESNext, 73 | jsx: ttp.ts.JsxEmit.Preserve, 74 | }, 75 | }).outputText; 76 | } 77 | 78 | let result = ''; 79 | // For d.ts files we just generate the AST 80 | if (!inputSource) { 81 | result = ttp.generate(ast, options.generator); 82 | } 83 | // For .tsx? files we transpile them and inject the proptypes 84 | else { 85 | const injected = ttp.inject(ast, inputSource, options.injector); 86 | if (!injected) { 87 | throw new Error('Injection failed'); 88 | } 89 | 90 | result = injected; 91 | } 92 | 93 | //#region Check generated and/or injected proptypes 94 | const propTypes = prettier.format(result, { 95 | ...prettierConfig, 96 | filepath: outputPath, 97 | }); 98 | 99 | if (fs.existsSync(outputPath)) { 100 | expect(propTypes.replace(/\r?\n/g, '\n')).toMatch( 101 | fs.readFileSync(outputPath, 'utf8').replace(/\r?\n/g, '\n') 102 | ); 103 | } else { 104 | fs.writeFileSync(outputPath, propTypes); 105 | } 106 | //#endregion 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /test/injector/all-props-ignored/input.tsx: -------------------------------------------------------------------------------- 1 | export function Foo(props: { className: string }) { 2 | return
; 3 | } 4 | -------------------------------------------------------------------------------- /test/injector/all-props-ignored/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../../types'; 2 | 3 | const options: TestOptions = { 4 | injector: { 5 | shouldInclude() { 6 | return false; 7 | }, 8 | }, 9 | }; 10 | 11 | export default options; 12 | -------------------------------------------------------------------------------- /test/injector/all-props-ignored/output.js: -------------------------------------------------------------------------------- 1 | export function Foo(props) { 2 | return
; 3 | } 4 | -------------------------------------------------------------------------------- /test/injector/all-props-ignored/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { "type": "PropTypeNode", "name": "className", "propType": { "type": "StringNode" } } 9 | ] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/injector/should-include-component-based/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface SnackBarProps { 4 | /** 5 | * Some hints about why this is useful 6 | */ 7 | id?: string; 8 | /** 9 | * some prop that is inherited which we don't care about here 10 | */ 11 | onChange?: () => void; 12 | } 13 | 14 | export function Snackbar(props: SnackBarProps) { 15 | return
; 16 | } 17 | 18 | export function SomeOtherComponent(props: { id?: string }) { 19 | return
; 20 | } 21 | -------------------------------------------------------------------------------- /test/injector/should-include-component-based/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../../types'; 2 | 3 | const options: TestOptions = { 4 | injector: { 5 | shouldInclude({ component, prop }) { 6 | if (component.name === 'Snackbar' && prop.name === 'id') { 7 | return true; 8 | } 9 | }, 10 | }, 11 | }; 12 | 13 | export default options; 14 | -------------------------------------------------------------------------------- /test/injector/should-include-component-based/output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | function Snackbar(props) { 4 | return
; 5 | } 6 | 7 | Snackbar.propTypes = { 8 | /** 9 | * Some hints about why this is useful 10 | */ 11 | id: PropTypes.string, 12 | }; 13 | 14 | export { Snackbar }; 15 | export function SomeOtherComponent(props) { 16 | return
; 17 | } 18 | -------------------------------------------------------------------------------- /test/injector/should-include-component-based/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Snackbar", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "id", 11 | "jsDoc": "Some hints about why this is useful", 12 | "propType": { 13 | "type": "UnionNode", 14 | "types": [{ "type": "UndefinedNode" }, { "type": "StringNode" }] 15 | } 16 | }, 17 | { 18 | "type": "PropTypeNode", 19 | "name": "onChange", 20 | "jsDoc": "some prop that is inherited which we don't care about here", 21 | "propType": { 22 | "type": "UnionNode", 23 | "types": [{ "type": "UndefinedNode" }, { "type": "FunctionNode" }] 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | "type": "ComponentNode", 30 | "name": "SomeOtherComponent", 31 | "types": [ 32 | { 33 | "type": "PropTypeNode", 34 | "name": "id", 35 | "propType": { 36 | "type": "UnionNode", 37 | "types": [{ "type": "UndefinedNode" }, { "type": "StringNode" }] 38 | } 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/injector/should-include-filename-based/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // it's technically not correct since this descripts props the component 4 | // sees not just the one available to the user. We're abusing this to provide 5 | // some concrete documentation for `key` regarding this component 6 | export interface SnackBarProps extends React.HTMLAttributes { 7 | /** 8 | * some hints about state reset that relates to prop of this component 9 | */ 10 | key?: any; 11 | } 12 | 13 | export function Snackbar(props: SnackBarProps) { 14 | return
; 15 | } 16 | 17 | // here we don't care about `key` 18 | export function SomeOtherComponent(props: { children?: React.ReactNode }) { 19 | return
{props.children}
; 20 | } 21 | -------------------------------------------------------------------------------- /test/injector/should-include-filename-based/options.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { TestOptions } from '../../types'; 3 | 4 | const options: TestOptions = { 5 | injector: { 6 | includeUnusedProps: true, 7 | shouldInclude: ({ prop }) => { 8 | let isLocallyTyped = false; 9 | prop.filenames.forEach((filename) => { 10 | if (!path.relative(__dirname, filename).startsWith('..')) { 11 | isLocallyTyped = true; 12 | } 13 | }); 14 | return isLocallyTyped; 15 | }, 16 | }, 17 | }; 18 | 19 | export default options; 20 | -------------------------------------------------------------------------------- /test/injector/should-include-filename-based/output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | function Snackbar(props) { 4 | return
; 5 | } 6 | // here we don't care about `key` 7 | 8 | Snackbar.propTypes = { 9 | /** 10 | * some hints about state reset that relates to prop of this component 11 | */ 12 | key: PropTypes.any, 13 | }; 14 | 15 | export { Snackbar }; 16 | function SomeOtherComponent(props) { 17 | return
{props.children}
; 18 | } 19 | 20 | SomeOtherComponent.propTypes = { 21 | children: PropTypes.node, 22 | }; 23 | 24 | export { SomeOtherComponent }; 25 | -------------------------------------------------------------------------------- /test/injector/string-props/input.tsx: -------------------------------------------------------------------------------- 1 | export default function Dialog(props: { 'aria-describedby': string }) { 2 | const { 'aria-describedby': ariaDescribedby } = props; 3 | return
; 4 | } 5 | -------------------------------------------------------------------------------- /test/injector/string-props/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | function Dialog(props) { 3 | const { 'aria-describedby': ariaDescribedby } = props; 4 | return
; 5 | } 6 | 7 | Dialog.propTypes = { 8 | 'aria-describedby': PropTypes.string.isRequired, 9 | }; 10 | 11 | export default Dialog; 12 | -------------------------------------------------------------------------------- /test/injector/string-props/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Dialog", 7 | "types": [ 8 | { "type": "PropTypeNode", "name": "aria-describedby", "propType": { "type": "StringNode" } } 9 | ] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/injector/whitelisted-props/input.tsx: -------------------------------------------------------------------------------- 1 | export default function Foo(props: { className: string }) { 2 | return
; 3 | } 4 | -------------------------------------------------------------------------------- /test/injector/whitelisted-props/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../../types'; 2 | 3 | const options: TestOptions = { 4 | injector: { 5 | shouldInclude() { 6 | return true; 7 | }, 8 | }, 9 | }; 10 | 11 | export default options; 12 | -------------------------------------------------------------------------------- /test/injector/whitelisted-props/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | function Foo(props) { 3 | return
; 4 | } 5 | 6 | Foo.propTypes = { 7 | className: PropTypes.string.isRequired, 8 | }; 9 | 10 | export default Foo; 11 | -------------------------------------------------------------------------------- /test/injector/whitelisted-props/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { "type": "PropTypeNode", "name": "className", "propType": { "type": "StringNode" } } 9 | ] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/mixed-literals/input.tsx: -------------------------------------------------------------------------------- 1 | interface GridProps { 2 | spacing?: 'initial' | 1 | 2 | 3 | 4 | 5 | 'auto'; 3 | } 4 | 5 | export default function Grid(props: GridProps) { 6 | const { spacing } = props; 7 | return
spacing: {spacing}
; 8 | } 9 | -------------------------------------------------------------------------------- /test/mixed-literals/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | function Grid(props) { 3 | const { spacing } = props; 4 | return
spacing: {spacing}
; 5 | } 6 | 7 | Grid.propTypes = { 8 | spacing: PropTypes.oneOf(['auto', 'initial', 1, 2, 3, 4, 5]), 9 | }; 10 | 11 | export default Grid; 12 | -------------------------------------------------------------------------------- /test/mixed-literals/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Grid", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "spacing", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [ 14 | { "type": "UndefinedNode" }, 15 | { "type": "LiteralNode", "value": "\"initial\"" }, 16 | { "type": "LiteralNode", "value": 1 }, 17 | { "type": "LiteralNode", "value": 2 }, 18 | { "type": "LiteralNode", "value": 3 }, 19 | { "type": "LiteralNode", "value": 4 }, 20 | { "type": "LiteralNode", "value": 5 }, 21 | { "type": "LiteralNode", "value": "\"auto\"" } 22 | ] 23 | }, 24 | "filenames": {} 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/options-test/input.tsx: -------------------------------------------------------------------------------- 1 | type DeepOptions = { 2 | PropB: string; 3 | }; 4 | 5 | type Options = { 6 | /** 7 | * This jsdoc will be ignored 8 | */ 9 | PropA: string; 10 | TestProps: DeepOptions; 11 | }; 12 | 13 | export default function Foo(props: Options) { 14 | const { PropA, TestProps } = props; 15 | return
; 16 | } 17 | -------------------------------------------------------------------------------- /test/options-test/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../types'; 2 | 3 | const options: TestOptions = { 4 | parser: { 5 | shouldResolveObject({ name }) { 6 | if (name.endsWith('Props')) { 7 | return false; 8 | } 9 | }, 10 | }, 11 | injector: { 12 | includeJSDoc: false, 13 | comment: 'Proptypes generated automatically', 14 | }, 15 | }; 16 | 17 | export default options; 18 | -------------------------------------------------------------------------------- /test/options-test/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | function Foo(props) { 3 | const { PropA, TestProps } = props; 4 | return
; 5 | } 6 | 7 | Foo.propTypes = { 8 | // Proptypes generated automatically 9 | PropA: PropTypes.string.isRequired, 10 | TestProps: PropTypes.object.isRequired, 11 | }; 12 | 13 | export default Foo; 14 | -------------------------------------------------------------------------------- /test/options-test/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "PropA", 11 | "jsDoc": "This jsdoc will be ignored", 12 | "propType": { "type": "StringNode" } 13 | }, 14 | { "type": "PropTypeNode", "name": "TestProps", "propType": { "type": "ObjectNode" } } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/overloaded-function-component/input.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | interface ButtonProps { 3 | variant?: string; 4 | } 5 | 6 | interface Component { 7 | (props: ButtonProps): JSX.Element; 8 | (props: { component: C } & ButtonProps): JSX.Element; 9 | } 10 | 11 | // a component using overloading and intersection of function signature 12 | declare const ButtonBase: Component & ((props: { href: string } & ButtonProps) => JSX.Element); 13 | -------------------------------------------------------------------------------- /test/overloaded-function-component/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../types'; 2 | 3 | const options: TestOptions = { 4 | parser: { 5 | checkDeclarations: true, 6 | }, 7 | }; 8 | 9 | export default options; 10 | -------------------------------------------------------------------------------- /test/overloaded-function-component/output.js: -------------------------------------------------------------------------------- 1 | ButtonBase.propTypes = { 2 | component: PropTypes.elementType, 3 | href: PropTypes.string, 4 | variant: PropTypes.string, 5 | }; 6 | -------------------------------------------------------------------------------- /test/overloaded-function-component/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "ButtonBase", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "variant", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [{ "type": "UndefinedNode" }, { "type": "StringNode" }] 14 | }, 15 | "filenames": {} 16 | }, 17 | { 18 | "type": "PropTypeNode", 19 | "name": "component", 20 | "propType": { 21 | "type": "UnionNode", 22 | "types": [ 23 | { "type": "ElementNode", "elementType": "elementType" }, 24 | { "type": "UndefinedNode" } 25 | ] 26 | }, 27 | "filenames": {} 28 | }, 29 | { 30 | "type": "PropTypeNode", 31 | "name": "href", 32 | "propType": { 33 | "type": "UnionNode", 34 | "types": [{ "type": "StringNode" }, { "type": "UndefinedNode" }] 35 | }, 36 | "filenames": {} 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /test/react-memo/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type Props = { 4 | MemoComponent: React.MemoExoticComponent; 5 | OptionalMemoComponent?: React.MemoExoticComponent; 6 | Component: React.Component; 7 | OptionalComponent?: React.Component; 8 | }; 9 | 10 | export default function Foo(props: Props) { 11 | const { MemoComponent, Component, OptionalComponent, OptionalMemoComponent } = props; 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /test/react-memo/output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | function Foo(props) { 4 | const { MemoComponent, Component, OptionalComponent, OptionalMemoComponent } = props; 5 | return ; 6 | } 7 | 8 | Foo.propTypes = { 9 | Component: PropTypes.elementType.isRequired, 10 | MemoComponent: PropTypes.elementType.isRequired, 11 | OptionalComponent: PropTypes.elementType, 12 | OptionalMemoComponent: PropTypes.elementType, 13 | }; 14 | 15 | export default Foo; 16 | -------------------------------------------------------------------------------- /test/react-memo/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Foo", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "MemoComponent", 11 | "propType": { "type": "ElementNode", "elementType": "elementType" }, 12 | "filenames": {} 13 | }, 14 | { 15 | "type": "PropTypeNode", 16 | "name": "OptionalMemoComponent", 17 | "propType": { 18 | "type": "UnionNode", 19 | "types": [ 20 | { "type": "UndefinedNode" }, 21 | { "type": "ElementNode", "elementType": "elementType" } 22 | ] 23 | }, 24 | "filenames": {} 25 | }, 26 | { 27 | "type": "PropTypeNode", 28 | "name": "Component", 29 | "propType": { "type": "ElementNode", "elementType": "elementType" }, 30 | "filenames": {} 31 | }, 32 | { 33 | "type": "PropTypeNode", 34 | "name": "OptionalComponent", 35 | "propType": { 36 | "type": "UnionNode", 37 | "types": [ 38 | { "type": "UndefinedNode" }, 39 | { "type": "ElementNode", "elementType": "elementType" } 40 | ] 41 | }, 42 | "filenames": {} 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /test/reconcile-prop-types/input.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | children: React.ReactNode; 5 | } 6 | 7 | export default function Component(props: Props): JSX.Element; 8 | -------------------------------------------------------------------------------- /test/reconcile-prop-types/input.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as PropTypes from 'prop-types'; 3 | import { chainPropTypes } from 'some-utils-module'; 4 | 5 | function Component(props) { 6 | const { children } = props; 7 | return ( 8 | 11 | ); 12 | } 13 | 14 | Component.propTypes = { 15 | children: chainPropTypes(PropTypes.node.isRequired, (props) => { 16 | const summary = React.Children.toArray(props.children)[0]; 17 | if (isFragment(summary)) { 18 | return new Error('Not accepting Fragments'); 19 | } 20 | 21 | if (!React.isValidElement(summary)) { 22 | return new Error('First child must be an element'); 23 | } 24 | 25 | return null; 26 | }), 27 | }; 28 | 29 | export default Component; 30 | -------------------------------------------------------------------------------- /test/reconcile-prop-types/options.ts: -------------------------------------------------------------------------------- 1 | import { TestOptions } from '../types'; 2 | 3 | const options: TestOptions = { 4 | injector: { 5 | removeExistingPropTypes: true, 6 | reconcilePropTypes: (prop, previous: any, generated) => { 7 | const isCustomValidator = previous !== undefined && !previous.startsWith('PropTypes'); 8 | 9 | if (isCustomValidator) { 10 | return previous; 11 | } 12 | return generated; 13 | }, 14 | }, 15 | }; 16 | 17 | export default options; 18 | -------------------------------------------------------------------------------- /test/reconcile-prop-types/output.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as PropTypes from 'prop-types'; 3 | import { chainPropTypes } from 'some-utils-module'; 4 | 5 | function Component(props) { 6 | const { children } = props; 7 | return ( 8 | 11 | ); 12 | } 13 | 14 | Component.propTypes = { 15 | children: chainPropTypes(PropTypes.node.isRequired, (props) => { 16 | const summary = React.Children.toArray(props.children)[0]; 17 | if (isFragment(summary)) { 18 | return new Error('Not accepting Fragments'); 19 | } 20 | 21 | if (!React.isValidElement(summary)) { 22 | return new Error('First child must be an element'); 23 | } 24 | 25 | return null; 26 | }), 27 | }; 28 | 29 | export default Component; 30 | -------------------------------------------------------------------------------- /test/reconcile-prop-types/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Component", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "children", 11 | "propType": { 12 | "type": "UnionNode", 13 | "types": [{ "type": "ElementNode", "elementType": "node" }, { "type": "UndefinedNode" }] 14 | } 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /test/recursive-type/input.d.ts: -------------------------------------------------------------------------------- 1 | interface TreeNode { 2 | id: number; 3 | label: string; 4 | children: TreeNode[]; 5 | } 6 | 7 | export default function Tree(props: TreeNode): JSX.Element; 8 | -------------------------------------------------------------------------------- /test/recursive-type/output.js: -------------------------------------------------------------------------------- 1 | Tree.propTypes = { 2 | children: PropTypes.arrayOf(PropTypes.object).isRequired, 3 | id: PropTypes.number.isRequired, 4 | label: PropTypes.string.isRequired, 5 | }; 6 | -------------------------------------------------------------------------------- /test/recursive-type/output.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ProgramNode", 3 | "body": [ 4 | { 5 | "type": "ComponentNode", 6 | "name": "Tree", 7 | "types": [ 8 | { 9 | "type": "PropTypeNode", 10 | "name": "id", 11 | "propType": { "type": "NumericNode" }, 12 | "filenames": {} 13 | }, 14 | { 15 | "type": "PropTypeNode", 16 | "name": "label", 17 | "propType": { "type": "StringNode" }, 18 | "filenames": {} 19 | }, 20 | { 21 | "type": "PropTypeNode", 22 | "name": "children", 23 | "propType": { "type": "ArrayNode", "arrayType": { "type": "ObjectNode" } }, 24 | "filenames": {} 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/sort-unions/input.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type Breakpoint = 'xs' | 'md' | 'xl'; 4 | 5 | export interface Props { 6 | /** 7 | * will be sorted alphanumeric 8 | */ 9 | color?: 'inherit' | 'default' | 'primary' | 'secondary'; 10 | /** 11 | * will be sorted by viewport size descending 12 | */ 13 | only?: Breakpoint | Breakpoint[]; 14 | } 15 | 16 | export default function Hidden(props: Props): JSX.Element; 17 | -------------------------------------------------------------------------------- /test/sort-unions/input.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function Hidden(props) { 4 | const { color, only } = props; 5 | 6 | return