├── .appveyor.yml ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── History.md ├── LICENSE ├── README.md ├── jest.config.js ├── jest.setup.js ├── package.json ├── src ├── bin.ts ├── dom.ts ├── env.ts ├── index.ts └── util.ts ├── tests ├── bin.test.ts ├── fixtures │ ├── bin │ │ ├── output │ │ │ ├── other.d.ts │ │ │ └── test.d.ts │ │ ├── test.d.ts │ │ └── test.js │ ├── normal │ │ ├── class │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── custom.1 │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── mod │ │ │ │ ├── index.d.ts │ │ │ │ └── index.js │ │ ├── custom │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── other.js │ │ │ └── test.js │ │ ├── esm.1 │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── esm.2 │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── other.js │ │ ├── esm │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── exports.1 │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── exports │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── function.1 │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── function │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── jsdoc.1 │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── jsdoc │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── object │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── prototype.1 │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ └── prototype │ │ │ ├── check.ts │ │ │ ├── index.d.ts │ │ │ └── index.js │ └── plugin │ │ ├── cookies │ │ ├── check.ts │ │ ├── index.d.ts │ │ └── index.js │ │ ├── egg-core │ │ ├── check.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── lib │ │ │ ├── egg.js │ │ │ ├── lifecycle.js │ │ │ ├── loader │ │ │ ├── context_loader.js │ │ │ ├── egg_loader.js │ │ │ ├── file_loader.js │ │ │ └── mixin │ │ │ │ ├── config.js │ │ │ │ ├── controller.js │ │ │ │ ├── custom.js │ │ │ │ ├── extend.js │ │ │ │ ├── middleware.js │ │ │ │ ├── plugin.js │ │ │ │ ├── router.js │ │ │ │ └── service.js │ │ │ └── utils │ │ │ ├── base_context_class.js │ │ │ ├── index.js │ │ │ ├── sequencify.js │ │ │ └── timing.js │ │ ├── egg-router │ │ ├── check.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── lib │ │ │ ├── egg_router.js │ │ │ ├── layer.js │ │ │ ├── router.js │ │ │ └── utils.js │ │ ├── mm │ │ ├── check.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── mm.js │ │ └── package.json │ │ ├── mus │ │ ├── check.ts │ │ ├── compile │ │ │ ├── ast.js │ │ │ ├── compile.js │ │ │ ├── constant.js │ │ │ ├── parser.js │ │ │ └── processor.js │ │ ├── index.d.ts │ │ ├── index.js │ │ └── utils │ │ │ ├── filters.js │ │ │ └── utils.js │ │ ├── ndir │ │ ├── index.d.ts │ │ ├── index.js │ │ └── lib │ │ │ └── ndir.js │ │ └── urllib │ │ ├── check.ts │ │ ├── detect_proxy_agent.js │ │ ├── get_proxy_from_uri.js │ │ ├── httpclient.js │ │ ├── httpclient2.js │ │ ├── index.d.ts │ │ ├── index.js │ │ └── urllib.js ├── index.test.ts └── util.test.ts ├── tsconfig.json ├── tslint.json └── typings └── index.d.ts /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm install 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run ci 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ##### Checklist 12 | 13 | 14 | - [ ] `npm test` passes 15 | - [ ] tests and/or benchmarks are included 16 | - [ ] documentation is changed or added 17 | - [ ] commit message follows commit guidelines 18 | 19 | ##### Affected core subsystem(s) 20 | 21 | 22 | 23 | ##### Description of change 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional eslint cache 43 | .eslintcache 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | # Output of 'npm pack' 49 | *.tgz 50 | 51 | # Yarn Integrity file 52 | .yarn-integrity 53 | *lock 54 | 55 | # dotenv environment variables file 56 | .env 57 | 58 | # next.js build output 59 | .next 60 | package-lock.json 61 | dist/ 62 | tests/fixtures/**/check.js 63 | tests/fixtures/**/tsconfig.json 64 | .history/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | install: 7 | - npm install 8 | os: 9 | - osx 10 | - linux 11 | script: 12 | - npm run ci 13 | after_script: 14 | - npminstall codecov && codecov -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug.node.autoAttach": "on", 3 | "files.exclude": { 4 | "tests/fixtures/**/check.js": true, 5 | "tests/fixtures/**/tsconfig.json": true 6 | } 7 | } -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.3.3 / 2019-05-14 3 | ================== 4 | 5 | **fixes** 6 | * [[`8abf082`](http://github.com/whxaxes/js2dts/commit/8abf082f68378dd7ed868235eec6f40e57843144)] - fix: handling scoped package names (#12) (wangshijun <>) 7 | 8 | **others** 9 | * [[`b37fd8b`](http://github.com/whxaxes/js2dts/commit/b37fd8bdb2e383f4ccf2fd07015a3b5ec02114a4)] - chore: upgrade test case (wanghx <>) 10 | 11 | 0.3.2 / 2019-03-11 12 | ================== 13 | 14 | * fix: some bugs in esm (#11) 15 | 16 | 0.3.1 / 2019-03-07 17 | ================== 18 | 19 | * fix: add flags 20 | 21 | 0.3.0 / 2019-03-06 22 | ================== 23 | 24 | * feat: add prefix in code 25 | 26 | 0.2.1 / 2019-03-06 27 | ================== 28 | 29 | * fix: type error in jsdoc 30 | * fix: symlink error 31 | 32 | 0.2.0 / 2019-02-17 33 | ================== 34 | 35 | * feat: support typedef for jsdoc (#7) 36 | * fix: support parenthesized type (#6) 37 | 38 | 0.1.5 / 2019-02-16 39 | ================== 40 | 41 | * fix: uniqId should reset 42 | 43 | 0.1.4 / 2019-02-15 44 | ================== 45 | 46 | **fixes** 47 | * [[`2b2b562`](http://github.com/whxaxes/js2dts/commit/2b2b562c888799271ab985c4e322c4b0859ccbf4)] - fix: fix path error in window (#5) (吖猩 <>) 48 | 49 | 0.1.2 / 2019-02-15 50 | ================== 51 | 52 | * fix: add lodash to deps (#4) 53 | 54 | 0.1.1 / 2019-02-14 55 | ================== 56 | 57 | * fix: export & jsdoc (#3) 58 | 59 | 0.1.0 / 2019-02-13 60 | ================== 61 | 62 | * fix: token type was ignored 63 | * feat: Optimize (#2) 64 | 65 | 0.0.2 / 2019-02-10 66 | ================== 67 | 68 | * fix: from lib check 69 | * fix: fix duplicate 70 | 71 | 0.0.1 / 2019-02-10 72 | ================== 73 | 74 | * feat: first implement(#1) 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 吖猩 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 | # js2dts 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Package Quality](http://npm.packagequality.com/shield/js2dts.svg)](http://packagequality.com/#?package=js2dts) 5 | [![Build Status][travis-image]][travis-url] 6 | [![Appveyor status][appveyor-image]][appveyor-url] 7 | [![Test coverage][codecov-image]][codecov-url] 8 | [![NPM download][download-image]][download-url] 9 | 10 | [npm-image]: https://img.shields.io/npm/v/js2dts.svg?style=flat-square 11 | [npm-url]: https://npmjs.org/package/js2dts 12 | [travis-url]: https://travis-ci.org/whxaxes/js2dts 13 | [travis-image]: http://img.shields.io/travis/whxaxes/js2dts.svg 14 | [appveyor-url]: https://ci.appveyor.com/project/whxaxes/js2dts/branch/master 15 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/github/whxaxes/js2dts?branch=master&svg=true 16 | [codecov-image]: https://codecov.io/gh/whxaxes/js2dts/branch/master/graph/badge.svg 17 | [codecov-url]: https://codecov.io/gh/whxaxes/js2dts 18 | [download-image]: https://img.shields.io/npm/dm/js2dts.svg?style=flat-square 19 | [download-url]: https://npmjs.org/package/js2dts 20 | 21 | Generate dts for javascript 22 | 23 | 24 | > TS has support generating d.ts for js by flags(`allowJs` and `declarations`) in [version 3.7](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#--declaration-and---allowjs) . So this project will not maintained anymore. ( eg. `tsc --allowJs --declaration --emitDeclarationOnly myfile.js` ) 25 | 26 | 27 | 28 | ## Install 29 | 30 | ``` 31 | npm i js2dts -g 32 | ``` 33 | 34 | or 35 | 36 | ``` 37 | yarn global add js2dts 38 | ``` 39 | 40 | ## Usage 41 | 42 | ``` 43 | Usage: j2d [options] 44 | 45 | Options: 46 | -v, --version output the version number 47 | -d, --dist [path] Create dts to giving path (default to the same dir as file) 48 | -t, --terminal Output the result to terminal instead of writing file to disk 49 | --no-prefix The generated code will has no prefix 50 | --ignore-private Private properties are also being export 51 | -h, --help output usage information 52 | ``` 53 | 54 | Example 55 | 56 | ```bash 57 | $ j2d ./test.js 58 | ``` 59 | 60 | or 61 | 62 | ```bash 63 | $ js2dts ./test.js 64 | ``` 65 | 66 | ## Example 67 | 68 | source code 69 | 70 | ```js 71 | // function/index.js 72 | module.exports = superName; 73 | 74 | /** 75 | * super function 76 | * 77 | * @param {String} bbb 123123 78 | */ 79 | function superName(bbb, ccc = module.exports.test) { 80 | return '123123'; 81 | } 82 | 83 | module.exports.test = () => 123123; 84 | module.exports.test2 = { 85 | abc: 123, 86 | }; 87 | ``` 88 | 89 | output dts 90 | 91 | ```typescript 92 | // function/index.d.ts 93 | interface T104 { 94 | test: () => number; 95 | test2: _Function.T105; 96 | } 97 | /** 98 | * super function 99 | * 100 | * @param {String} bbb 123123 101 | */ 102 | declare function superName(bbb: string, ccc?: () => number): string; 103 | declare const _Function: typeof superName & T104; 104 | declare namespace _Function { 105 | export interface T105 { 106 | abc: number; 107 | } 108 | } 109 | export = _Function; 110 | ``` 111 | 112 | More example: [tests/fixtures/**/*.d.ts](https://github.com/whxaxes/js2dts/tree/master/tests/fixtures) 113 | 114 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | setupTestFrameworkScriptFile: './jest.setup.js', 4 | testEnvironment: 'node', 5 | verbose: true, 6 | testMatch: [ 7 | '**/tests/**/*.test.ts', 8 | ], 9 | coveragePathIgnorePatterns: [ 10 | '/node_modules/', 11 | 'dist/dom.js' 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(10000); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js2dts", 3 | "version": "0.3.3", 4 | "description": "dts generator for js", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "bin": { 8 | "j2d": "dist/bin.js", 9 | "js2dts": "dist/bin.js" 10 | }, 11 | "scripts": { 12 | "build": "del ./dist && tsc -d", 13 | "build:w": "npm run build -- -w", 14 | "test": "npm run build && jest --no-watchman", 15 | "cov": "npm run test -- --coverage", 16 | "ci": "tsc && jest --coverage --no-watchman", 17 | "prepublish": "npm run build" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/whxaxes/js2dts.git" 22 | }, 23 | "keywords": [ 24 | "js2dts", 25 | "typescript" 26 | ], 27 | "files": [ 28 | "dist" 29 | ], 30 | "author": "whxaxes ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/whxaxes/js2dts/issues" 34 | }, 35 | "publishConfig": { 36 | "registry": "https://registry.npmjs.org/" 37 | }, 38 | "homepage": "https://github.com/whxaxes/js2dts#readme", 39 | "dependencies": { 40 | "commander": "^2.19.0", 41 | "lodash": "^4.17.11", 42 | "mkdirp": "^0.5.1", 43 | "tslib": "^1.9.3", 44 | "typescript": "^3.2.2" 45 | }, 46 | "devDependencies": { 47 | "@types/chokidar": "^1.7.4", 48 | "@types/commander": "^2.12.2", 49 | "@types/debug": "^0.0.30", 50 | "@types/del": "^3.0.0", 51 | "@types/globby": "^6.1.0", 52 | "@types/jest": "^23.3.12", 53 | "@types/lodash": "^4.14.120", 54 | "@types/mkdirp": "^0.5.2", 55 | "coffee": "^5.2.1", 56 | "del": "^3.0.0", 57 | "del-cli": "^1.1.0", 58 | "egg": "^2.15.1", 59 | "globby": "^8.0.1", 60 | "jest": "^23.6.0", 61 | "reselect": "^4.0.0", 62 | "ts-jest": "^23.10.5", 63 | "tslint": "^5.9.1", 64 | "tslint-config-egg": "^1.0.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import { Command } from 'commander'; 4 | import packInfo from '../package.json'; 5 | import assert from 'assert'; 6 | import path from 'path'; 7 | import { normalizeDtsUrl } from './util'; 8 | import mkdirp from 'mkdirp'; 9 | import fs from 'fs'; 10 | import { create, CreateDtsFlags } from './'; 11 | 12 | const program = new Command() 13 | .version(packInfo.version, '-v, --version') 14 | .usage('[options] ') 15 | .option('-d, --dist [path]', 'Create dts to giving path (default to the same dir as file)') 16 | .option('-t, --terminal', 'Output the result to terminal instead of writing file to disk') 17 | .option('--no-prefix', 'The generated code will has no prefix') 18 | .option('--ignore-private', 'Private properties are also being export'); 19 | 20 | program.parse(process.argv); 21 | 22 | let file = program.args[0]; 23 | assert(file, 'file can not be empty'); 24 | 25 | file = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file); 26 | assert(fs.existsSync(file), `${file} not found`); 27 | 28 | // handle dist option 29 | let dist = program.dist; 30 | if (dist) { 31 | if (dist.endsWith('/')) { 32 | // dir 33 | dist += path.basename(file, path.extname(file)); 34 | } 35 | dist = normalizeDtsUrl(dist); 36 | dist = path.isAbsolute(dist) ? dist : path.resolve(process.cwd(), dist); 37 | mkdirp.sync(path.dirname(dist)); 38 | } 39 | 40 | // handle flag option 41 | let flags = CreateDtsFlags.None; 42 | if (program.ignorePrivate) { 43 | flags |= CreateDtsFlags.IgnorePrivate; 44 | } 45 | 46 | // create dts env 47 | const env = create(file, { 48 | dist, 49 | flags, 50 | }); 51 | 52 | if (program.terminal) { 53 | console.log(env.toString()); 54 | } else { 55 | env.write(program.noPrefix ? '' : `// Generate by [${packInfo.name}@${packInfo.version}](${packInfo.homepage})`); 56 | } 57 | -------------------------------------------------------------------------------- /src/env.ts: -------------------------------------------------------------------------------- 1 | import { CreateDtsFlags, CreateOptions, PlainObj, ExportFlags } from './'; 2 | import ts from 'typescript'; 3 | import * as dom from './dom'; 4 | import path from 'path'; 5 | import fs from 'fs'; 6 | 7 | export interface Declaration { 8 | import: dom.Import[]; 9 | export: dom.TopLevelDeclaration[]; 10 | fragment: dom.NamespaceMember[]; 11 | } 12 | 13 | export interface ImportCacheElement { 14 | default?: string; 15 | list: Array<{ name: string; as?: string }>; 16 | from: string; 17 | realPath: string; 18 | } 19 | 20 | // runtime env 21 | export interface Env { 22 | file: string; 23 | dist: string; 24 | uniqId: number; 25 | flags: CreateDtsFlags; 26 | program: ts.Program; 27 | checker: ts.TypeChecker; 28 | sourceFile: ts.SourceFile; 29 | declaration: Declaration; 30 | importCache: { [key: string]: ImportCacheElement }; 31 | declareCache: Array<{ node: ts.Declaration, name: string }>; 32 | publicNames: PlainObj; 33 | exportDefaultName?: string; 34 | exportNamespace?: dom.NamespaceDeclaration; 35 | interfaceList: dom.InterfaceDeclaration[]; 36 | deps: { [key: string]: { env: Env; namespace: dom.NamespaceDeclaration } }; 37 | ambientModNames: string[]; 38 | exportFlags: ExportFlags; 39 | toString: () => string; 40 | write: (prefix?: string) => string; 41 | } 42 | 43 | export let env: Env; 44 | let envCache: { [key: string]: Env } = {}; 45 | const envStack: Env[] = []; 46 | 47 | export function getTopEnv() { 48 | return envStack.length ? envStack[0] : env; 49 | } 50 | 51 | // get opt from topEnv 52 | function getTopEnvOpt(key: T, def: Env[T]) { 53 | return envStack.length ? envStack[0][key] : def; 54 | } 55 | 56 | // get env from cache 57 | export function getEnv(file: string) { 58 | return envCache[file]; 59 | } 60 | 61 | // create env 62 | export function createEnv(file: string, options: CreateOptions = {}) { 63 | const program = ts.createProgram([ file ], { 64 | target: ts.ScriptTarget.ES2017, 65 | module: ts.ModuleKind.CommonJS, 66 | allowJs: true, 67 | noErrorTruncation: true, 68 | }); 69 | 70 | // save env 71 | if (env) { 72 | envStack.push(env); 73 | } 74 | 75 | const checker = program.getTypeChecker(); 76 | const sourceFile = program.getSourceFile(file)!; 77 | const ambientMods = checker.getAmbientModules(); 78 | const ambientModNames = ambientMods.map(mod => mod.escapedName.toString().replace(/^"|"$/g, '')); 79 | 80 | // init env object 81 | env = { 82 | file, 83 | dist: options.dist || `${path.dirname(file)}/${path.basename(file, path.extname(file))}.d.ts`, 84 | deps: {}, 85 | program, 86 | checker, 87 | sourceFile, 88 | declaration: { 89 | import: [], 90 | fragment: [], 91 | export: [], 92 | }, 93 | ambientModNames, 94 | declareCache: [], 95 | interfaceList: [], 96 | exportFlags: ExportFlags.None, 97 | 98 | // common obj in env tree 99 | uniqId: getTopEnvOpt('uniqId', 100), 100 | flags: getTopEnvOpt('flags', (options.flags || CreateDtsFlags.None)), 101 | publicNames: getTopEnvOpt('publicNames', {}), 102 | importCache: getTopEnvOpt('importCache', {}), 103 | 104 | // get string 105 | toString() { 106 | return [ 107 | dom.emit(this.declaration.import), 108 | dom.emit(this.declaration.fragment), 109 | dom.emit(this.declaration.export), 110 | ].join(''); 111 | }, 112 | 113 | // write file 114 | write(prefix?: string) { 115 | const content = this.toString(); 116 | fs.writeFileSync(this.dist, `${prefix ? `${prefix}\n\n` : ''}${content}`); 117 | return content; 118 | }, 119 | }; 120 | 121 | // cache env 122 | envCache[file] = env; 123 | } 124 | 125 | export function popEnv() { 126 | const response = env; 127 | 128 | // restore env 129 | env = envStack.pop()!; 130 | 131 | if (!envStack.length) { 132 | // clean cache 133 | envCache = {}; 134 | } 135 | 136 | return response; 137 | } 138 | -------------------------------------------------------------------------------- /tests/bin.test.ts: -------------------------------------------------------------------------------- 1 | import coffee from 'coffee'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | 5 | describe('bin.test.ts', () => { 6 | const binPath = require.resolve('../dist/bin'); 7 | 8 | it('should works with -h correctly', async () => { 9 | return coffee.fork(binPath, [ '-h' ]) 10 | // .debug() 11 | .expect('stdout', /Usage/) 12 | .expect('stdout', /--dist \[path]/) 13 | .expect('code', 0) 14 | .end(); 15 | }); 16 | 17 | it('should works with -t correctly', async () => { 18 | return coffee.fork(binPath, [ path.resolve(__dirname, './fixtures/bin/test.js'), '-t' ]) 19 | // .debug() 20 | .expect('stdout', /a: number;/) 21 | .expect('stdout', /b: string;/) 22 | .expect('code', 0) 23 | .end(); 24 | }); 25 | 26 | it('should write file correctly', async () => { 27 | await coffee.fork(binPath, [ path.resolve(__dirname, './fixtures/bin/test.js') ]) 28 | // .debug() 29 | .expect('code', 0) 30 | .end(); 31 | 32 | fs.existsSync(path.resolve(__dirname, './fixtures/bin/test.d.ts')); 33 | }); 34 | 35 | it('should work with -d dir correctly', async () => { 36 | await coffee.fork(binPath, [ 37 | './fixtures/bin/test.js', 38 | '-d', 39 | './fixtures/bin/output/', 40 | ], { cwd: __dirname }) 41 | // .debug() 42 | .expect('code', 0) 43 | .end(); 44 | 45 | fs.existsSync(path.resolve(__dirname, './fixtures/bin/output/test.d.ts')); 46 | }); 47 | 48 | it('should work with -d file correctly', async () => { 49 | await coffee.fork(binPath, [ 50 | './fixtures/bin/test.js', 51 | '-d', 52 | path.resolve(__dirname, './fixtures/bin/output/other.d.ts'), 53 | ], { cwd: __dirname }) 54 | // .debug() 55 | .expect('code', 0) 56 | .end(); 57 | 58 | fs.existsSync(path.resolve(__dirname, './fixtures/bin/output/other.d.ts')); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/fixtures/bin/output/other.d.ts: -------------------------------------------------------------------------------- 1 | // Generate by [js2dts@0.3.2](https://github.com/whxaxes/js2dts#readme) 2 | 3 | declare const _Test: _Test.T100; 4 | declare namespace _Test { 5 | export interface T100 { 6 | a: number; 7 | b: string; 8 | } 9 | } 10 | export = _Test; 11 | -------------------------------------------------------------------------------- /tests/fixtures/bin/output/test.d.ts: -------------------------------------------------------------------------------- 1 | // Generate by [js2dts@0.3.2](https://github.com/whxaxes/js2dts#readme) 2 | 3 | declare const _Test: _Test.T100; 4 | declare namespace _Test { 5 | export interface T100 { 6 | a: number; 7 | b: string; 8 | } 9 | } 10 | export = _Test; 11 | -------------------------------------------------------------------------------- /tests/fixtures/bin/test.d.ts: -------------------------------------------------------------------------------- 1 | // Generate by [js2dts@0.3.2](https://github.com/whxaxes/js2dts#readme) 2 | 3 | declare const _Test: _Test.T100; 4 | declare namespace _Test { 5 | export interface T100 { 6 | a: number; 7 | b: string; 8 | } 9 | } 10 | export = _Test; 11 | -------------------------------------------------------------------------------- /tests/fixtures/bin/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | a: 123, 3 | b: '123', 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/normal/class/check.ts: -------------------------------------------------------------------------------- 1 | import MyTest = require('./'); 2 | 3 | const myTest = new MyTest({ name: '123' }); 4 | myTest.on('check', () => {}); 5 | myTest.emit('check'); 6 | myTest.opt.name.trim(); 7 | myTest.getCount().toFixed(); 8 | 9 | const myTest2 = MyTest.getInstance({ name: '123' }); 10 | myTest2.getCount().toFixed(); 11 | -------------------------------------------------------------------------------- /tests/fixtures/normal/class/index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | /** 3 | * my test class 4 | */ 5 | declare class MyTest extends EventEmitter { 6 | opt: _Class.T100; 7 | /** 8 | * test class 9 | * 10 | * @param {Object} options test 11 | * @param {String} options.name test 12 | */ 13 | constructor(options: _Class.T100); 14 | /** 15 | * test class 16 | * 17 | * @param {Object} opt test 18 | * @param {String} opt.name test 19 | */ 20 | static getInstance(opt: _Class.T100): MyTest; 21 | getCount(): number; 22 | } 23 | declare const _Class: typeof MyTest; 24 | declare namespace _Class { 25 | export interface T100 { 26 | name: string; 27 | } 28 | } 29 | export = _Class; 30 | -------------------------------------------------------------------------------- /tests/fixtures/normal/class/index.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | 3 | /** 4 | * my test class 5 | */ 6 | class MyTest extends events.EventEmitter { 7 | /** 8 | * test class 9 | * 10 | * @param {Object} options test 11 | * @param {String} options.name test 12 | */ 13 | constructor(options) { 14 | super(); 15 | this.opt = options; 16 | this._dd = 123; 17 | } 18 | 19 | /** 20 | * test class 21 | * 22 | * @param {Object} opt test 23 | * @param {String} opt.name test 24 | */ 25 | static getInstance(opt) { 26 | return new MyTest(opt); 27 | } 28 | 29 | getCount() { 30 | return this.listenerCount(); 31 | } 32 | 33 | } 34 | 35 | module.exports = MyTest; -------------------------------------------------------------------------------- /tests/fixtures/normal/custom.1/check.ts: -------------------------------------------------------------------------------- 1 | import { abc, getJSDoc, util, mod, egg, Application, Agent } from './'; 2 | import * as assert from 'assert'; 3 | 4 | util.getSymbol({} as any); 5 | getJSDoc({} as any); 6 | mod.default().toFixed(); 7 | abc.trim(); 8 | 9 | assert(egg); 10 | assert(Application); 11 | assert(Agent); 12 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom.1/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Node, JSDoc } from 'typescript'; 2 | import * as DistUtil from '../../../../dist/util'; 3 | import * as ModIndex from './mod/index'; 4 | import * as egg_1 from 'egg'; 5 | import { Application as Application_1, Agent as Agent_1 } from 'egg'; 6 | /** @type {String} abc */ 7 | export const abc: string; 8 | declare function getJSDoc_1(node: Node): JSDoc; 9 | export const getJSDoc: typeof getJSDoc_1; 10 | export const util: typeof DistUtil; 11 | export const mod: typeof ModIndex; 12 | export const egg: typeof egg_1; 13 | export const Application: typeof Application_1; 14 | export const Agent: typeof Agent_1; 15 | export default function T100(): number; 16 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom.1/index.js: -------------------------------------------------------------------------------- 1 | const util = require('../../../../dist/util'); 2 | const egg = require('egg'); 3 | 4 | /** @type {String} abc */ 5 | exports.abc = '123'; 6 | exports.getJSDoc = util.getJSDoc; 7 | exports.util = util; 8 | exports.mod = require('./mod'); 9 | 10 | exports.egg = egg; 11 | exports.Application = egg.Application; 12 | exports.Agent = egg.Agent; 13 | exports.default = () => 123; 14 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom.1/mod/index.d.ts: -------------------------------------------------------------------------------- 1 | declare function abc(): number; 2 | 3 | export default abc; 4 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom.1/mod/index.js: -------------------------------------------------------------------------------- 1 | function abc() { 2 | return 123; 3 | } 4 | 5 | module.exports = abc; 6 | module.exports.default = abc; 7 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom/check.ts: -------------------------------------------------------------------------------- 1 | import * as MyTest from './'; 2 | 3 | const m = new MyTest({ 4 | name: '123', 5 | }); 6 | 7 | m.on('asd', () => {}); 8 | 9 | const mm = new MyTest.MyTest({ 10 | name: '123', 11 | }); 12 | 13 | mm.on('ad', () => {}); 14 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as events from 'events'; 2 | import { EventEmitter } from 'events'; 3 | import * as globby from 'globby'; 4 | import * as http from 'http'; 5 | import { Server } from 'http'; 6 | /** 7 | * my test class 8 | */ 9 | declare class MyTest extends EventEmitter { 10 | opt: _Custom.T100; 11 | /** 12 | * test class 13 | * 14 | * @param {Object} options test 15 | * @param {String} options.name test 16 | */ 17 | constructor(options: _Custom.T100); 18 | /** 19 | * test class 20 | * 21 | * @param {Object} opt test 22 | * @param {String} opt.name test 23 | */ 24 | static getInstance(opt: _Custom.T100): MyTest; 25 | getCount(): number; 26 | } 27 | interface T101 { 28 | MyTest: typeof MyTest; 29 | glob: typeof globby; 30 | test: typeof _Test; 31 | http: typeof http; 32 | Server: typeof Server; 33 | events: typeof events; 34 | } 35 | declare namespace _Test { 36 | // tests/fixtures/normal/custom/test.js 37 | export const a: number; 38 | export const b: string; 39 | export function c(): number; 40 | } 41 | declare const _Custom: typeof MyTest & T101; 42 | declare namespace _Custom { 43 | export interface T100 { 44 | name: string; 45 | } 46 | } 47 | export = _Custom; 48 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom/index.js: -------------------------------------------------------------------------------- 1 | const MyTest = require('./other'); 2 | const glob = require('globby'); 3 | const http = require('http'); 4 | 5 | module.exports = MyTest; 6 | module.exports.MyTest = MyTest; 7 | module.exports.glob = glob; 8 | module.exports.test = require('./test'); 9 | module.exports.http = http; 10 | module.exports.Server = http.Server; 11 | module.exports.events = require('events'); 12 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom/other.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | 3 | /** 4 | * my test class 5 | */ 6 | class MyTest extends events.EventEmitter { 7 | /** 8 | * test class 9 | * 10 | * @param {Object} options test 11 | * @param {String} options.name test 12 | */ 13 | constructor(options) { 14 | super(); 15 | this.opt = options; 16 | } 17 | 18 | /** 19 | * test class 20 | * 21 | * @param {Object} opt test 22 | * @param {String} opt.name test 23 | */ 24 | static getInstance(opt) { 25 | return new MyTest(opt); 26 | } 27 | 28 | getCount() { 29 | return this.listenerCount(); 30 | } 31 | } 32 | 33 | module.exports = MyTest; 34 | -------------------------------------------------------------------------------- /tests/fixtures/normal/custom/test.js: -------------------------------------------------------------------------------- 1 | exports.a = 123; 2 | exports.b = '123'; 3 | exports.c = () => 123; 4 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm.1/index.d.ts: -------------------------------------------------------------------------------- 1 | import { OutputSelector } from 'reselect'; 2 | export function sliceSelector(state: any): any; 3 | export const valueSliceSelector: OutputSelector any>; 4 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm.1/index.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const sliceSelector = state => state['sliceName']; 4 | export const valueSliceSelector = createSelector(sliceSelector, (slice) => { 5 | return slice.value; 6 | }); -------------------------------------------------------------------------------- /tests/fixtures/normal/esm.2/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface T100 { 2 | test(): any; 3 | } 4 | declare const _default_1: T100; 5 | export default _default_1; 6 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm.2/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './other'; 2 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm.2/other.js: -------------------------------------------------------------------------------- 1 | const abc = { 2 | test() { 3 | return abc; 4 | } 5 | } 6 | 7 | export default abc; 8 | export const bbc = 123; 9 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm/index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | export default class Test extends EventEmitter { 3 | test(): this; 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/normal/esm/index.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export default class Test extends EventEmitter { 4 | test() { 5 | return this.on('test', {}); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports.1/check.ts: -------------------------------------------------------------------------------- 1 | import { development, abb, other, abbb, cool, otherFn } from './'; 2 | import * as assert from 'assert'; 3 | 4 | development.fastReady.valueOf(); 5 | development.ignoreDirs.slice(0); 6 | development.reloadOnDebug.valueOf(); 7 | development.watchDirs.slice(0); 8 | 9 | abb(); 10 | other.toFixed(); 11 | abbb.toFixed(); 12 | cool.trim(); 13 | assert(otherFn().FSWatcher); 14 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports.1/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as chokidar from 'chokidar'; 2 | export interface T100 { 3 | watchDirs: any[]; 4 | ignoreDirs: any[]; 5 | fastReady: boolean; 6 | reloadOnDebug: boolean; 7 | overrideDefault: boolean; 8 | reloadPattern?: any; 9 | } 10 | /** 11 | * development config 12 | * 13 | * @member Config#development 14 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 15 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 16 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 17 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 18 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 19 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 20 | */ 21 | 22 | /** 23 | * development config1 24 | * 25 | * @member Config#development 26 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 27 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 28 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 29 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 30 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 31 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 32 | */ 33 | 34 | /** 35 | * development config2 36 | * 37 | * @member Config#development 38 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 39 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 40 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 41 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 42 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 43 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 44 | */ 45 | export const development: T100; 46 | export function abb(): void; 47 | export default function myFn(): typeof chokidar; 48 | export const other: number; 49 | export const otherFn: typeof myFn; 50 | export const abbb: number; 51 | export const cool: string; 52 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports.1/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * development config 4 | * 5 | * @member Config#development 6 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 7 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 8 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 9 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 10 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 11 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 12 | */ 13 | 14 | /** 15 | * development config1 16 | * 17 | * @member Config#development 18 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 19 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 20 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 21 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 22 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 23 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 24 | */ 25 | 26 | /** 27 | * development config2 28 | * 29 | * @member Config#development 30 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 31 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 32 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 33 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 34 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 35 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 36 | */ 37 | export const development = { 38 | watchDirs: [], 39 | ignoreDirs: [], 40 | fastReady: false, 41 | reloadOnDebug: true, 42 | overrideDefault: false, 43 | reloadPattern: undefined, 44 | } 45 | 46 | export const abb = () => {} 47 | 48 | export default function myFn() { 49 | return require('chokidar'); 50 | } 51 | 52 | exports.other = 123123; 53 | 54 | exports.otherFn = myFn; 55 | 56 | const abbb = 123; 57 | const accc = '123'; 58 | 59 | export { abbb, accc as cool }; 60 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports/check.ts: -------------------------------------------------------------------------------- 1 | import * as obj from './'; 2 | 3 | obj.development.watchDirs = [ 11, 2 ]; 4 | obj.development.reloadOnDebug.valueOf(); 5 | obj.development.ignoreDirs.slice(0); 6 | obj.test.toFixed(); 7 | obj.xxx.toFixed(); 8 | obj.blabla.trim(); 9 | obj.a().toFixed(); 10 | obj.b().toFixed(); 11 | obj.c()().toFixed(); 12 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface T100 { 2 | watchDirs: any[]; 3 | ignoreDirs: any[]; 4 | fastReady: boolean; 5 | reloadOnDebug: boolean; 6 | overrideDefault: boolean; 7 | reloadPattern?: any; 8 | } 9 | /** 10 | * @member Config#development 11 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 12 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 13 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 14 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 15 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 16 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 17 | */ 18 | export const development: T100; 19 | export const test: number; 20 | export const blabla: string; 21 | export const xxx: number; 22 | export function a(): number; 23 | export function b(): number; 24 | export function c(): () => number; 25 | -------------------------------------------------------------------------------- /tests/fixtures/normal/exports/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @member Config#development 4 | * @property {Array} watchDirs - dirs needed watch, when files under these change, application will reload, use relative path 5 | * @property {Array} ignoreDirs - dirs don't need watch, including subdirectories, use relative path 6 | * @property {Boolean} fastReady - don't wait all plugins ready, default is false. 7 | * @property {Boolean} reloadOnDebug - whether reload on debug, default is true. 8 | * @property {Boolean} overrideDefault - whether override default watchDirs, default is false. 9 | * @property {Array|String} reloadPattern - whether to reload, use https://github.com/sindresorhus/multimatch 10 | */ 11 | exports.development = { 12 | watchDirs: [], 13 | ignoreDirs: [], 14 | fastReady: false, 15 | reloadOnDebug: true, 16 | overrideDefault: false, 17 | reloadPattern: undefined, 18 | }; 19 | 20 | exports.test = 123123; 21 | exports.blabla = '12313'; 22 | 23 | const myExport = exports; 24 | myExport.xxx = '123'; 25 | myExport.xxx = 123; 26 | 27 | 28 | exports.a = () => 123123; 29 | exports.b = () => '123123'; 30 | exports.c = () => exports.a; 31 | exports.b = () => 222; 32 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function.1/check.ts: -------------------------------------------------------------------------------- 1 | import * as fn from './index'; 2 | 3 | fn('123123').trim(); 4 | fn.test().toFixed(); 5 | fn.test2.abc.toFixed(); 6 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function.1/index.d.ts: -------------------------------------------------------------------------------- 1 | interface T100 { 2 | test: () => number; 3 | test2: _Function1.T101; 4 | } 5 | /** 6 | * super function 7 | * 8 | * @param {String} bbb 123123 9 | */ 10 | declare function superName(bbb: string, ccc?: () => number): string; 11 | declare const _Function1: typeof superName & T100; 12 | declare namespace _Function1 { 13 | export interface T101 { 14 | abc: number; 15 | } 16 | } 17 | export = _Function1; 18 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function.1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = superName; 2 | 3 | /** 4 | * super function 5 | * 6 | * @param {String} bbb 123123 7 | */ 8 | function superName(bbb, ccc = module.exports.test) { 9 | return '123123'; 10 | } 11 | 12 | module.exports.test = () => 123123; 13 | module.exports.test2 = { 14 | abc: 123, 15 | }; 16 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function/check.ts: -------------------------------------------------------------------------------- 1 | import * as fn from './index'; 2 | 3 | fn('123123').trim(); 4 | fn.test().toFixed(); 5 | fn.test2.abc.toFixed(); 6 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function/index.d.ts: -------------------------------------------------------------------------------- 1 | interface T100 { 2 | test: () => number; 3 | test2: _Function.T101; 4 | } 5 | declare const _Function: ((bbb: string) => string) & T100; 6 | declare namespace _Function { 7 | export interface T101 { 8 | abc: number; 9 | } 10 | } 11 | export = _Function; 12 | -------------------------------------------------------------------------------- /tests/fixtures/normal/function/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * super function 3 | * 4 | * @param {String} bbb 123123 5 | */ 6 | module.exports = function superName(bbb) { 7 | return '123123'; 8 | } 9 | 10 | module.exports.test = () => 123123; 11 | module.exports.test2 = { 12 | abc: 123, 13 | }; 14 | -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc.1/check.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whxaxes/js2dts/133ad2c878d677bfc60ddcbe640d93b68ad929c1/tests/fixtures/normal/jsdoc.1/check.ts -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc.1/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface CheckOption { 2 | cwd: string; 3 | fix?: boolean; 4 | exitLevel?: "none" | "info" | "warn" | "error"; 5 | exitLevel2?: "none" | "info" | "warn" | "error"; 6 | root?: string[]; 7 | defaultIndex?: string[]; 8 | preset?: string; 9 | pattern?: "none" | "info" | "warn" | "error"; 10 | ignore?: string | string[]; 11 | } 12 | /** @typedef {typeof LOG_LEVELS} LOG_KEYS */ 13 | 14 | /** 15 | * @typedef {Object} CheckOption 16 | * @property {String} CheckOption.cwd 17 | * @property {Boolean} [CheckOption.fix] 18 | * @property {keyof LOG_KEYS} [CheckOption.exitLevel] 19 | * @property {keyof LOG_LEVELS} [CheckOption.exitLevel2] 20 | * @property {Array} [CheckOption.root] 21 | * @property {Array} [CheckOption.defaultIndex] 22 | * @property {CheckOption['cwd']} [CheckOption.preset] 23 | * @property {'error' | 'info' | 'warn' | 'none'} [CheckOption.pattern] 24 | * @property {String | Array} [CheckOption.ignore] 25 | */ 26 | 27 | /** 28 | * @param {CheckOption} opt 29 | * @return {CheckOption} 30 | */ 31 | export function test(opt: CheckOption): CheckOption; 32 | -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc.1/index.js: -------------------------------------------------------------------------------- 1 | const LOG_LEVELS = { 2 | none: 0, 3 | info: 1, 4 | warn: 2, 5 | error: 3, 6 | }; 7 | 8 | /** @typedef {typeof LOG_LEVELS} LOG_KEYS */ 9 | 10 | /** 11 | * @typedef {Object} CheckOption 12 | * @property {String} CheckOption.cwd 13 | * @property {Boolean} [CheckOption.fix] 14 | * @property {keyof LOG_KEYS} [CheckOption.exitLevel] 15 | * @property {keyof LOG_LEVELS} [CheckOption.exitLevel2] 16 | * @property {Array} [CheckOption.root] 17 | * @property {Array} [CheckOption.defaultIndex] 18 | * @property {CheckOption['cwd']} [CheckOption.preset] 19 | * @property {'error' | 'info' | 'warn' | 'none'} [CheckOption.pattern] 20 | * @property {String | Array} [CheckOption.ignore] 21 | */ 22 | 23 | /** 24 | * @param {CheckOption} opt 25 | * @return {CheckOption} 26 | */ 27 | exports.test = opt => { 28 | return opt; 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc/check.ts: -------------------------------------------------------------------------------- 1 | import * as obj from './'; 2 | 3 | const t = new obj.MyTest(); 4 | t.fn({ a: '123' }).a.trim(); 5 | t.fn3(); 6 | t.fn4().next(); 7 | t.fn5().then(() => {}); 8 | t.fn6({ 9 | a: '123123', 10 | }); 11 | t.fn6({ 12 | a: '123123', 13 | b: { 14 | c: 123123, 15 | d: () => ({}), 16 | e: (async () => '123')(), 17 | f: true, 18 | }, 19 | }); 20 | t.fn6({ 21 | a: '123123', 22 | b: { 23 | c: 123123, 24 | d: () => ({}), 25 | e: (async () => '123')(), 26 | f: true, 27 | g: [ true ], 28 | h: [[ '123', true ]], 29 | i: { 30 | a: '123', 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface T100 { 2 | a: string; 3 | b?: string; 4 | cc?: Array; 5 | dd?: number | Array; 6 | } 7 | export interface T101 { 8 | [key: string]: any; 9 | } 10 | export interface FnObj { 11 | a: string; 12 | b?: { 13 | c: number; 14 | d: () => T101; 15 | e: Promise; 16 | f: string | boolean | boolean; 17 | g?: boolean[]; 18 | h?: Array>; 19 | i?: { 20 | a?: string; 21 | }; 22 | }; 23 | } 24 | declare class MyTest_1 { 25 | constructor(); 26 | /** 27 | * 1111 28 | * @param {Object} opt 29 | * @param {String} opt.a 123 30 | * @param {String} [opt.b] 123 31 | * @param {Array} [opt.cc] 123 32 | * @param {Array|Number>|Number} [opt.dd] 123 33 | */ 34 | fn(opt: T100): T100; 35 | fn3(): void; 36 | fn4(): IterableIterator; 37 | fn5(): Promise; 38 | /** 39 | * @typedef FnObj 40 | * @property {String} FnObj.a asd 41 | * @property {Object} [FnObj.b] asd 42 | * @property {Number} FnObj.b.c asd 43 | * @property {() => {}} FnObj.b.d asd 44 | * @property {Promise} FnObj.b.e asd 45 | * @property {Boolean|String} FnObj.b.f asd 46 | * @property {Array} [FnObj.b.g] asd 47 | * @property {Array>} [FnObj.b.h] asd 48 | * @property {Object} [FnObj.b.i] asd 49 | * @property {String} [FnObj.b.i.a] asd 50 | */ 51 | 52 | /** 53 | * @param {FnObj} opt 123 54 | */ 55 | fn6(opt: FnObj): FnObj; 56 | } 57 | export const MyTest: typeof MyTest_1; 58 | /** @type {{ [key: string]: string }} */ 59 | export const bbb: T101; 60 | -------------------------------------------------------------------------------- /tests/fixtures/normal/jsdoc/index.js: -------------------------------------------------------------------------------- 1 | exports.MyTest = class { 2 | constructor() {} 3 | 4 | /** 5 | * 1111 6 | * @param {Object} opt 7 | * @param {String} opt.a 123 8 | * @param {String} [opt.b] 123 9 | * @param {Array} [opt.cc] 123 10 | * @param {Array|Number>|Number} [opt.dd] 123 11 | */ 12 | fn(opt) { 13 | return opt; 14 | } 15 | 16 | /** 17 | * @private 18 | */ 19 | fn2() {} 20 | 21 | // test 123 22 | fn3() {} 23 | 24 | *fn4() { 25 | return '123123'; 26 | } 27 | 28 | async fn5() { 29 | return this.fn(); 30 | } 31 | 32 | /** 33 | * @typedef FnObj 34 | * @property {String} FnObj.a asd 35 | * @property {Object} [FnObj.b] asd 36 | * @property {Number} FnObj.b.c asd 37 | * @property {() => {}} FnObj.b.d asd 38 | * @property {Promise} FnObj.b.e asd 39 | * @property {Boolean|String} FnObj.b.f asd 40 | * @property {Array} [FnObj.b.g] asd 41 | * @property {Array>} [FnObj.b.h] asd 42 | * @property {Object} [FnObj.b.i] asd 43 | * @property {String} [FnObj.b.i.a] asd 44 | */ 45 | 46 | /** 47 | * @param {FnObj} opt 123 48 | */ 49 | fn6(opt) { 50 | return opt; 51 | } 52 | } 53 | 54 | /** @type {{ [key: string]: string }} */ 55 | exports.bbb = {}; 56 | 57 | /** @private */ 58 | exports.aaa = {}; 59 | -------------------------------------------------------------------------------- /tests/fixtures/normal/object/check.ts: -------------------------------------------------------------------------------- 1 | import * as object from './index'; 2 | import * as assert from 'assert'; 3 | 4 | object.aaaa.trim(); 5 | object.test.toFixed(); 6 | object.test2().trim(); 7 | object.test3().toFixed(); 8 | object.test4('123123').then(result => result.trim()); 9 | object.test5().then(result => result.trim()); 10 | object.test6()(123123).toFixed(); 11 | 12 | const test = object.test7('123'); 13 | test.ccc.toFixed(); 14 | assert(test.n === '123'); 15 | test.test().trim(); 16 | test.test2().toFixed(); 17 | 18 | object.test8().ctime.getFullYear(); 19 | object.test9().trim(); 20 | -------------------------------------------------------------------------------- /tests/fixtures/normal/object/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Stats } from 'fs'; 2 | interface T100 { 3 | test9: () => string; 4 | } 5 | declare function testFn(bb?: number): number; 6 | declare class BaseClass { 7 | test(): string; 8 | } 9 | declare class TestClass extends BaseClass { 10 | n: any; 11 | ccc: number; 12 | constructor(n: any); 13 | test2(): number; 14 | } 15 | declare const _Object: _Object.T101 & T100; 16 | declare namespace _Object { 17 | export interface T101 { 18 | test: number; 19 | aaaa: string; 20 | test2: () => string; 21 | test3: () => number; 22 | test4(abc?: string): Promise; 23 | /** 24 | * test 25 | * @returns {Promise} 26 | */ 27 | test5(): Promise; 28 | test6(): typeof testFn; 29 | test7(str: any): TestClass; 30 | test8(): Stats; 31 | } 32 | } 33 | export = _Object; 34 | -------------------------------------------------------------------------------- /tests/fixtures/normal/object/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function testFn(bb = 123) { 5 | return bb * 2; 6 | } 7 | 8 | class BaseClass { 9 | test() { 10 | return '123'; 11 | } 12 | } 13 | 14 | class TestClass extends BaseClass { 15 | constructor(n) { 16 | super(); 17 | this.n = n; 18 | this.ccc = this.test2(); 19 | } 20 | 21 | test2() { 22 | return 123; 23 | } 24 | } 25 | 26 | module.exports = { 27 | test: 123, 28 | aaaa: String(123123), 29 | 30 | test2: () => { 31 | return '123123'; 32 | }, 33 | 34 | test3: () => 123123, 35 | 36 | async test4(abc = '123123') { 37 | return abc; 38 | }, 39 | 40 | /** 41 | * test 42 | * @returns {Promise} 43 | */ 44 | test5() { 45 | return new Promise(resolve => { 46 | resolve('123123') 47 | }); 48 | }, 49 | 50 | test6() { 51 | return testFn; 52 | }, 53 | 54 | test7(str) { 55 | return new TestClass(str); 56 | }, 57 | 58 | test8() { 59 | return fs.lstatSync(path.resolve(__dirname, './index.js')); 60 | } 61 | }; 62 | 63 | module.exports.test9 = () => { 64 | return '132123123'; 65 | } 66 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype.1/check.ts: -------------------------------------------------------------------------------- 1 | import * as router from './'; 2 | 3 | router.on('asd', () => {}); 4 | router.emit('asd'); 5 | router.get('/xxx'); 6 | router.head('/xxx'); 7 | router.post('/xxx').trim(); 8 | router.ddd().toFixed(); 9 | router.eee().toFixed(); 10 | router.ccc().toFixed(); 11 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype.1/index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | /** 3 | * Router Class 4 | * @param {Object} opt option 5 | * @param {String} opt.path path 6 | */ 7 | declare class Router extends EventEmitter { 8 | constructor(opt: _Prototype1.T100); 9 | get(url: any): any; 10 | /** 11 | * @param {String} url url 12 | */ 13 | post(url: string): string; 14 | head(url: any): any; 15 | bbb(url: any): any; 16 | static aaa(): number; 17 | ddd(): number; 18 | eee(): number; 19 | ccc(): number; 20 | static url(url: any): any; 21 | opt: any; 22 | params: any; 23 | } 24 | declare const _Prototype1: Router; 25 | declare namespace _Prototype1 { 26 | export interface T100 { 27 | path: string; 28 | } 29 | } 30 | export = _Prototype1; 31 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype.1/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Router Class 4 | * @param {Object} opt option 5 | * @param {String} opt.path path 6 | */ 7 | function Router(opt) { 8 | this.opt = opt; 9 | this.params = {}; 10 | } 11 | 12 | Router.prototype = new (require('events').EventEmitter)() 13 | 14 | const rp = Router.prototype; 15 | 16 | rp.get = function(url) { 17 | return url; 18 | } 19 | 20 | /** 21 | * @param {String} url url 22 | */ 23 | Router.prototype.post = function(url) { 24 | return url; 25 | } 26 | 27 | const rpp = rp; 28 | 29 | if (rpp) { 30 | rpp.head = function(url) { 31 | return url; 32 | } 33 | } else if (rp) { 34 | rpp.bbb = function(url) { 35 | return url; 36 | } 37 | } else { 38 | Router.aaa = () => 123; 39 | } 40 | 41 | rpp.ccc = rpp.ddd = rpp.eee = () => 123; 42 | 43 | // static method 44 | Router.url = url => { 45 | return url; 46 | } 47 | 48 | module.exports = new Router(); 49 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype/check.ts: -------------------------------------------------------------------------------- 1 | import * as router from './'; 2 | 3 | router.go().trim(); 4 | router.get('/xxx'); 5 | router.head('/xxx'); 6 | router.post('/xxx').trim(); 7 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Router Class 3 | * @param {Object} opt option 4 | * @param {String} opt.path path 5 | */ 6 | declare class Router { 7 | constructor(opt: _Prototype.T100); 8 | go(): string; 9 | get(url: any): any; 10 | /** 11 | * @param {String} url url 12 | */ 13 | post(url: string): string; 14 | head(url: any): any; 15 | static url(url: any): any; 16 | opt: any; 17 | params: any; 18 | } 19 | declare const _Prototype: Router & _Prototype.T101; 20 | declare namespace _Prototype { 21 | export interface T100 { 22 | path: string; 23 | } 24 | export interface T101 { 25 | go(): string; 26 | } 27 | } 28 | export = _Prototype; 29 | -------------------------------------------------------------------------------- /tests/fixtures/normal/prototype/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Router Class 4 | * @param {Object} opt option 5 | * @param {String} opt.path path 6 | */ 7 | function Router(opt) { 8 | this.opt = opt; 9 | this.params = {}; 10 | } 11 | 12 | Router.prototype = { 13 | go() { 14 | return '1'; 15 | } 16 | }; 17 | 18 | const rp = Router.prototype; 19 | 20 | rp.get = function(url) { 21 | return url; 22 | } 23 | 24 | /** 25 | * @param {String} url url 26 | */ 27 | Router.prototype.post = function(url) { 28 | return url; 29 | } 30 | 31 | const rpp = rp; 32 | 33 | rpp.head = function(url) { 34 | return url; 35 | } 36 | 37 | // static method 38 | Router.url = url => { 39 | return url; 40 | } 41 | 42 | module.exports = new Router(); 43 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/cookies/check.ts: -------------------------------------------------------------------------------- 1 | import Cookies = require('./'); 2 | 3 | const cookies = new Cookies({}, {}, {}); 4 | 5 | cookies.get('key', {}); 6 | cookies.set('key', {}, {}); 7 | const cookie = new Cookies.Cookie('aaa', {}, {}); 8 | 9 | cookie.path.trim(); 10 | cookie.httpOnly.valueOf(); 11 | cookie.sameSite.valueOf(); 12 | cookie.secure.valueOf(); 13 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/cookies/index.d.ts: -------------------------------------------------------------------------------- 1 | declare class Cookie { 2 | constructor(name: any, value: any, attrs: any); 3 | path: string; 4 | expires?: any; 5 | domain?: any; 6 | httpOnly: boolean; 7 | sameSite: boolean; 8 | secure: boolean; 9 | overwrite: boolean; 10 | toString(): string; 11 | toHeader(): string; 12 | name: any; 13 | value: any; 14 | } 15 | declare class Cookies { 16 | constructor(request: any, response: any, options: any); 17 | get(name: any, opts: any): any; 18 | set(name: any, value: any, opts: any): Cookies; 19 | static express(keys: any): (req: any, res: any, next: any) => void; 20 | static connect(keys: any): (req: any, res: any, next: any) => void; 21 | static Cookie: typeof Cookie; 22 | request: any; 23 | response: any; 24 | keys: any; 25 | secure: any; 26 | } 27 | declare const _Cookies: typeof Cookies; 28 | export = _Cookies; 29 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/cookies/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * cookies 3 | * Copyright(c) 2014 Jed Schmidt, http://jed.is/ 4 | * Copyright(c) 2015-2016 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | var deprecate = require('depd')('cookies') 11 | var Keygrip = require('keygrip') 12 | var http = require('http') 13 | var cache = {} 14 | 15 | /** 16 | * RegExp to match field-content in RFC 7230 sec 3.2 17 | * 18 | * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 19 | * field-vchar = VCHAR / obs-text 20 | * obs-text = %x80-FF 21 | */ 22 | 23 | var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; 24 | 25 | /** 26 | * RegExp to match Same-Site cookie attribute value. 27 | */ 28 | 29 | var sameSiteRegExp = /^(?:lax|strict)$/i 30 | 31 | function Cookies(request, response, options) { 32 | if (!(this instanceof Cookies)) return new Cookies(request, response, options) 33 | 34 | this.secure = undefined 35 | this.request = request 36 | this.response = response 37 | 38 | if (options) { 39 | if (Array.isArray(options)) { 40 | // array of key strings 41 | deprecate('"keys" argument; provide using options {"keys": [...]}') 42 | this.keys = new Keygrip(options) 43 | } else if (options.constructor && options.constructor.name === 'Keygrip') { 44 | // any keygrip constructor to allow different versions 45 | deprecate('"keys" argument; provide using options {"keys": keygrip}') 46 | this.keys = options 47 | } else { 48 | this.keys = Array.isArray(options.keys) ? new Keygrip(options.keys) : options.keys 49 | this.secure = options.secure 50 | } 51 | } 52 | } 53 | 54 | Cookies.prototype.get = function(name, opts) { 55 | var sigName = name + ".sig" 56 | , header, match, value, remote, data, index 57 | , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys 58 | 59 | header = this.request.headers["cookie"] 60 | if (!header) return 61 | 62 | match = header.match(getPattern(name)) 63 | if (!match) return 64 | 65 | value = match[1] 66 | if (!opts || !signed) return value 67 | 68 | remote = this.get(sigName) 69 | if (!remote) return 70 | 71 | data = name + "=" + value 72 | if (!this.keys) throw new Error('.keys required for signed cookies'); 73 | index = this.keys.index(data, remote) 74 | 75 | if (index < 0) { 76 | this.set(sigName, null, {path: "/", signed: false }) 77 | } else { 78 | index && this.set(sigName, this.keys.sign(data), { signed: false }) 79 | return value 80 | } 81 | }; 82 | 83 | Cookies.prototype.set = function(name, value, opts) { 84 | var res = this.response 85 | , req = this.request 86 | , headers = res.getHeader("Set-Cookie") || [] 87 | , secure = this.secure !== undefined ? !!this.secure : req.protocol === 'https' || req.connection.encrypted 88 | , cookie = new Cookie(name, value, opts) 89 | , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys 90 | 91 | if (typeof headers == "string") headers = [headers] 92 | 93 | if (!secure && opts && opts.secure) { 94 | throw new Error('Cannot send secure cookie over unencrypted connection') 95 | } 96 | 97 | cookie.secure = secure 98 | if (opts && "secure" in opts) cookie.secure = opts.secure 99 | 100 | if (opts && "secureProxy" in opts) { 101 | deprecate('"secureProxy" option; use "secure" option, provide "secure" to constructor if needed') 102 | cookie.secure = opts.secureProxy 103 | } 104 | 105 | pushCookie(headers, cookie) 106 | 107 | if (opts && signed) { 108 | if (!this.keys) throw new Error('.keys required for signed cookies'); 109 | cookie.value = this.keys.sign(cookie.toString()) 110 | cookie.name += ".sig" 111 | pushCookie(headers, cookie) 112 | } 113 | 114 | var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader 115 | setHeader.call(res, 'Set-Cookie', headers) 116 | return this 117 | }; 118 | 119 | function Cookie(name, value, attrs) { 120 | if (!fieldContentRegExp.test(name)) { 121 | throw new TypeError('argument name is invalid'); 122 | } 123 | 124 | if (value && !fieldContentRegExp.test(value)) { 125 | throw new TypeError('argument value is invalid'); 126 | } 127 | 128 | value || (this.expires = new Date(0)) 129 | 130 | this.name = name 131 | this.value = value || "" 132 | 133 | for (var name in attrs) { 134 | this[name] = attrs[name] 135 | } 136 | 137 | if (this.path && !fieldContentRegExp.test(this.path)) { 138 | throw new TypeError('option path is invalid'); 139 | } 140 | 141 | if (this.domain && !fieldContentRegExp.test(this.domain)) { 142 | throw new TypeError('option domain is invalid'); 143 | } 144 | 145 | if (this.sameSite && this.sameSite !== true && !sameSiteRegExp.test(this.sameSite)) { 146 | throw new TypeError('option sameSite is invalid') 147 | } 148 | } 149 | 150 | Cookie.prototype.path = "/"; 151 | Cookie.prototype.expires = undefined; 152 | Cookie.prototype.domain = undefined; 153 | Cookie.prototype.httpOnly = true; 154 | Cookie.prototype.sameSite = false; 155 | Cookie.prototype.secure = false; 156 | Cookie.prototype.overwrite = false; 157 | 158 | Cookie.prototype.toString = function() { 159 | return this.name + "=" + this.value 160 | }; 161 | 162 | Cookie.prototype.toHeader = function() { 163 | var header = this.toString() 164 | 165 | if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge); 166 | 167 | if (this.path ) header += "; path=" + this.path 168 | if (this.expires ) header += "; expires=" + this.expires.toUTCString() 169 | if (this.domain ) header += "; domain=" + this.domain 170 | if (this.sameSite ) header += "; samesite=" + (this.sameSite === true ? 'strict' : this.sameSite.toLowerCase()) 171 | if (this.secure ) header += "; secure" 172 | if (this.httpOnly ) header += "; httponly" 173 | 174 | return header 175 | }; 176 | 177 | // back-compat so maxage mirrors maxAge 178 | Object.defineProperty(Cookie.prototype, 'maxage', { 179 | configurable: true, 180 | enumerable: true, 181 | get: function () { return this.maxAge }, 182 | set: function (val) { return this.maxAge = val } 183 | }); 184 | deprecate.property(Cookie.prototype, 'maxage', '"maxage"; use "maxAge" instead') 185 | 186 | function getPattern(name) { 187 | if (cache[name]) return cache[name] 188 | 189 | return cache[name] = new RegExp( 190 | "(?:^|;) *" + 191 | name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + 192 | "=([^;]*)" 193 | ) 194 | } 195 | 196 | function pushCookie(headers, cookie) { 197 | if (cookie.overwrite) { 198 | for (var i = headers.length - 1; i >= 0; i--) { 199 | if (headers[i].indexOf(cookie.name + '=') === 0) { 200 | headers.splice(i, 1) 201 | } 202 | } 203 | } 204 | 205 | headers.push(cookie.toHeader()) 206 | } 207 | 208 | Cookies.connect = Cookies.express = function(keys) { 209 | return function(req, res, next) { 210 | req.cookies = res.cookies = new Cookies(req, res, { 211 | keys: keys 212 | }) 213 | 214 | next() 215 | } 216 | } 217 | 218 | Cookies.Cookie = Cookie 219 | 220 | module.exports = Cookies 221 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/check.ts: -------------------------------------------------------------------------------- 1 | import { EggCore, EggLoader, utils, BaseContextClass } from './'; 2 | 3 | const eggCore = new EggCore(); 4 | eggCore.loader.eggPaths.slice(0); 5 | eggCore.ready(() => {}); 6 | eggCore.url('/xxx', {}).trim(); 7 | eggCore.baseDir.trim(); 8 | 9 | const loader = new EggLoader({ 10 | baseDir: 'xxx', 11 | app: {}, 12 | logger: {}, 13 | }); 14 | loader.eggPaths.slice(0); 15 | loader.loadToApp('/xxxx', 'xxx', {}); 16 | loader.loadFile('xxxxx'); 17 | 18 | utils.loadFile('xxxxx'); 19 | utils.methods.slice(0); 20 | 21 | const obj = new BaseContextClass({}); 22 | console.info(obj.config); 23 | console.info(obj.app); 24 | console.info(obj.service); 25 | console.info(obj.ctx); 26 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EggCore = require('./lib/egg'); 4 | const EggLoader = require('./lib/loader/egg_loader'); 5 | const BaseContextClass = require('./lib/utils/base_context_class'); 6 | const utils = require('./lib/utils'); 7 | 8 | module.exports = { 9 | EggCore, 10 | EggLoader, 11 | BaseContextClass, 12 | utils, 13 | }; 14 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/lifecycle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const is = require('is-type-of'); 4 | const assert = require('assert'); 5 | const getReady = require('get-ready'); 6 | const { Ready } = require('ready-callback'); 7 | const { EventEmitter } = require('events'); 8 | const debug = require('debug')('egg-core:lifecycle'); 9 | const INIT = Symbol('Lifycycle#init'); 10 | const INIT_READY = Symbol('Lifecycle#initReady'); 11 | const DELEGATE_READY_EVENT = Symbol('Lifecycle#delegateReadyEvent'); 12 | const REGISTER_BEFORE_CLOSE = Symbol('Lifecycle#registerBeforeClose'); 13 | const REGISTER_READY_CALLBACK = Symbol('Lifecycle#registerReadyCallback'); 14 | const CLOSE_SET = Symbol('Lifecycle#closeSet'); 15 | const IS_CLOSED = Symbol('Lifecycle#isClosed'); 16 | const BOOT_HOOKS = Symbol('Lifecycle#bootHooks'); 17 | const BOOTS = Symbol('Lifecycle#boots'); 18 | 19 | const utils = require('./utils'); 20 | 21 | class Lifecycle extends EventEmitter { 22 | 23 | /** 24 | * @param {object} options - options 25 | * @param {String} options.baseDir - the directory of application 26 | * @param {EggCore} options.app - Application instance 27 | * @param {Logger} options.logger - logger 28 | */ 29 | constructor(options) { 30 | super(); 31 | this.options = options; 32 | this[BOOT_HOOKS] = []; 33 | this[BOOTS] = []; 34 | this[CLOSE_SET] = new Set(); 35 | this[IS_CLOSED] = false; 36 | this[INIT] = false; 37 | getReady.mixin(this); 38 | 39 | this.timing.start('Application Start'); 40 | // get app timeout from env or use default timeout 10 second 41 | const eggReadyTimeoutEnv = Number.parseInt(process.env.EGG_READY_TIMEOUT_ENV || 10000); 42 | assert( 43 | Number.isInteger(eggReadyTimeoutEnv), 44 | `process.env.EGG_READY_TIMEOUT_ENV ${process.env.EGG_READY_TIMEOUT_ENV} should be able to parseInt.`); 45 | this.readyTimeout = eggReadyTimeoutEnv; 46 | 47 | this[INIT_READY](); 48 | this 49 | .on('ready_stat', data => { 50 | this.logger.info('[egg:core:ready_stat] end ready task %s, remain %j', data.id, data.remain); 51 | }) 52 | .on('ready_timeout', id => { 53 | this.logger.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', this.readyTimeout / 1000, id); 54 | }); 55 | 56 | this.ready(err => { 57 | this.triggerDidReady(err); 58 | this.timing.end('Application Start'); 59 | }); 60 | } 61 | 62 | get app() { 63 | return this.options.app; 64 | } 65 | 66 | get logger() { 67 | return this.options.logger; 68 | } 69 | 70 | get timing() { 71 | return this.app.timing; 72 | } 73 | 74 | legacyReadyCallback(name, opt) { 75 | return this.loadReady.readyCallback(name, opt); 76 | } 77 | 78 | addBootHook(hook) { 79 | assert(this[INIT] === false, 'do not add hook when lifecycle has been initialized'); 80 | this[BOOT_HOOKS].push(hook); 81 | } 82 | 83 | addFunctionAsBootHook(hook) { 84 | assert(this[INIT] === false, 'do not add hook when lifecycle has been initialized'); 85 | // app.js is exported as a function 86 | // call this function in configDidLoad 87 | this[BOOT_HOOKS].push(class Hook { 88 | constructor(app) { 89 | this.app = app; 90 | } 91 | configDidLoad() { 92 | hook(this.app); 93 | } 94 | }); 95 | } 96 | 97 | /** 98 | * init boots and trigger config did config 99 | */ 100 | init() { 101 | assert(this[INIT] === false, 'lifecycle have been init'); 102 | this[INIT] = true; 103 | this[BOOTS] = this[BOOT_HOOKS].map(t => new t(this.app)); 104 | this[REGISTER_BEFORE_CLOSE](); 105 | } 106 | 107 | registerBeforeStart(scope) { 108 | this[REGISTER_READY_CALLBACK](scope, this.loadReady, 'Before Start'); 109 | } 110 | 111 | registerBeforeClose(fn) { 112 | assert(is.function(fn), 'argument should be function'); 113 | assert(this[IS_CLOSED] === false, 'app has been closed'); 114 | this[CLOSE_SET].add(fn); 115 | } 116 | 117 | async close() { 118 | // close in reverse order: first created, last closed 119 | const closeFns = Array.from(this[CLOSE_SET]); 120 | for (const fn of closeFns.reverse()) { 121 | await utils.callFn(fn); 122 | this[CLOSE_SET].delete(fn); 123 | } 124 | // Be called after other close callbacks 125 | this.app.emit('close'); 126 | this.removeAllListeners(); 127 | this.app.removeAllListeners(); 128 | this[IS_CLOSED] = true; 129 | } 130 | 131 | triggerConfigWillLoad() { 132 | for (const boot of this[BOOTS]) { 133 | if (boot.configWillLoad) { 134 | boot.configWillLoad(); 135 | } 136 | } 137 | this.triggerConfigDidLoad(); 138 | } 139 | 140 | triggerConfigDidLoad() { 141 | for (const boot of this[BOOTS]) { 142 | if (boot.configDidLoad) { 143 | boot.configDidLoad(); 144 | } 145 | } 146 | this.triggerDidLoad(); 147 | } 148 | 149 | triggerDidLoad() { 150 | debug('register didLoad'); 151 | for (const boot of this[BOOTS]) { 152 | const didLoad = boot.didLoad && boot.didLoad.bind(boot); 153 | if (didLoad) { 154 | this[REGISTER_READY_CALLBACK](didLoad, this.loadReady, 'Did Load'); 155 | } 156 | } 157 | } 158 | 159 | triggerWillReady() { 160 | debug('register willReady'); 161 | this.bootReady.start(); 162 | for (const boot of this[BOOTS]) { 163 | const willReady = boot.willReady && boot.willReady.bind(boot); 164 | if (willReady) { 165 | this[REGISTER_READY_CALLBACK](willReady, this.bootReady, 'Will Ready'); 166 | } 167 | } 168 | } 169 | 170 | triggerDidReady(err) { 171 | debug('trigger didReady'); 172 | (async () => { 173 | for (const boot of this[BOOTS]) { 174 | if (boot.didReady) { 175 | try { 176 | await boot.didReady(err); 177 | } catch (e) { 178 | this.emit('error', e); 179 | } 180 | } 181 | } 182 | debug('trigger didReady done'); 183 | })(); 184 | } 185 | 186 | triggerServerDidReady() { 187 | (async () => { 188 | for (const boot of this[BOOTS]) { 189 | try { 190 | await utils.callFn(boot.serverDidReady, null, boot); 191 | } catch (e) { 192 | this.emit('error', e); 193 | } 194 | } 195 | })(); 196 | } 197 | 198 | [INIT_READY]() { 199 | this.loadReady = new Ready({ timeout: this.readyTimeout }); 200 | this[DELEGATE_READY_EVENT](this.loadReady); 201 | this.loadReady.ready(err => { 202 | debug('didLoad done'); 203 | if (err) { 204 | this.ready(err); 205 | } else { 206 | this.triggerWillReady(); 207 | } 208 | }); 209 | 210 | this.bootReady = new Ready({ timeout: this.readyTimeout, lazyStart: true }); 211 | this[DELEGATE_READY_EVENT](this.bootReady); 212 | this.bootReady.ready(err => { 213 | this.ready(err || true); 214 | }); 215 | } 216 | 217 | [DELEGATE_READY_EVENT](ready) { 218 | ready.once('error', err => ready.ready(err)); 219 | ready.on('ready_timeout', id => this.emit('ready_timeout', id)); 220 | ready.on('ready_stat', data => this.emit('ready_stat', data)); 221 | ready.on('error', err => this.emit('error', err)); 222 | } 223 | 224 | [REGISTER_BEFORE_CLOSE]() { 225 | for (const boot of this[BOOTS]) { 226 | const beforeClose = boot.beforeClose && boot.beforeClose.bind(boot); 227 | if (beforeClose) { 228 | this.registerBeforeClose(beforeClose); 229 | } 230 | } 231 | } 232 | 233 | [REGISTER_READY_CALLBACK](scope, ready, timingKeyPrefix) { 234 | if (!is.function(scope)) { 235 | throw new Error('boot only support function'); 236 | } 237 | 238 | // get filename from stack 239 | const name = utils.getCalleeFromStack(true, 4); 240 | const timingkey = `${timingKeyPrefix} in ` + utils.getResolvedFilename(name, this.app.baseDir); 241 | 242 | this.timing.start(timingkey); 243 | 244 | const done = ready.readyCallback(name); 245 | 246 | // ensure scope executes after load completed 247 | process.nextTick(() => { 248 | utils.callFn(scope).then(() => { 249 | done(); 250 | this.timing.end(timingkey); 251 | }, err => { 252 | done(err); 253 | this.timing.end(timingkey); 254 | }); 255 | }); 256 | } 257 | } 258 | 259 | module.exports = Lifecycle; 260 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/context_loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const is = require('is-type-of'); 5 | const FileLoader = require('./file_loader'); 6 | const CLASSLOADER = Symbol('classLoader'); 7 | const EXPORTS = FileLoader.EXPORTS; 8 | 9 | class ClassLoader { 10 | 11 | constructor(options) { 12 | assert(options.ctx, 'options.ctx is required'); 13 | const properties = options.properties; 14 | this._cache = new Map(); 15 | this._ctx = options.ctx; 16 | 17 | for (const property in properties) { 18 | this.defineProperty(property, properties[property]); 19 | } 20 | } 21 | 22 | defineProperty(property, values) { 23 | Object.defineProperty(this, property, { 24 | get() { 25 | let instance = this._cache.get(property); 26 | if (!instance) { 27 | instance = getInstance(values, this._ctx); 28 | this._cache.set(property, instance); 29 | } 30 | return instance; 31 | }, 32 | }); 33 | } 34 | } 35 | 36 | /** 37 | * Same as {@link FileLoader}, but it will attach file to `inject[fieldClass]`. The exports will be lazy loaded, such as `ctx.group.repository`. 38 | * @extends FileLoader 39 | * @since 1.0.0 40 | */ 41 | class ContextLoader extends FileLoader { 42 | 43 | /** 44 | * @constructor 45 | * @param {Object} options - options same as {@link FileLoader} 46 | * @param {String} options.fieldClass - determine the field name of inject object. 47 | */ 48 | constructor(options) { 49 | assert(options.property, 'options.property is required'); 50 | assert(options.inject, 'options.inject is required'); 51 | const target = options.target = {}; 52 | if (options.fieldClass) { 53 | options.inject[options.fieldClass] = target; 54 | } 55 | super(options); 56 | 57 | const app = this.options.inject; 58 | const property = options.property; 59 | 60 | // define ctx.service 61 | Object.defineProperty(app.context, property, { 62 | get() { 63 | // distinguish property cache, 64 | // cache's lifecycle is the same with this context instance 65 | // e.x. ctx.service1 and ctx.service2 have different cache 66 | if (!this[CLASSLOADER]) { 67 | this[CLASSLOADER] = new Map(); 68 | } 69 | const classLoader = this[CLASSLOADER]; 70 | 71 | let instance = classLoader.get(property); 72 | if (!instance) { 73 | instance = getInstance(target, this); 74 | classLoader.set(property, instance); 75 | } 76 | return instance; 77 | }, 78 | }); 79 | } 80 | } 81 | 82 | module.exports = ContextLoader; 83 | 84 | 85 | function getInstance(values, ctx) { 86 | // it's a directory when it has no exports 87 | // then use ClassLoader 88 | const Class = values[EXPORTS] ? values : null; 89 | let instance; 90 | if (Class) { 91 | if (is.class(Class)) { 92 | instance = new Class(ctx); 93 | } else { 94 | // it's just an object 95 | instance = Class; 96 | } 97 | // Can't set property to primitive, so check again 98 | // e.x. module.exports = 1; 99 | } else if (is.primitive(values)) { 100 | instance = values; 101 | } else { 102 | instance = new ClassLoader({ ctx, properties: values }); 103 | } 104 | return instance; 105 | } 106 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/file_loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const fs = require('fs'); 5 | const debug = require('debug')('egg-core:loader'); 6 | const path = require('path'); 7 | const globby = require('globby'); 8 | const is = require('is-type-of'); 9 | const deprecate = require('depd')('egg'); 10 | const utils = require('../utils'); 11 | const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH'); 12 | const EXPORTS = Symbol('EGG_LOADER_ITEM_EXPORTS'); 13 | 14 | const defaults = { 15 | directory: null, 16 | target: null, 17 | match: undefined, 18 | ignore: undefined, 19 | lowercaseFirst: false, 20 | caseStyle: 'camel', 21 | initializer: null, 22 | call: true, 23 | override: false, 24 | inject: undefined, 25 | filter: null, 26 | }; 27 | 28 | /** 29 | * Load files from directory to target object. 30 | * @since 1.0.0 31 | */ 32 | class FileLoader { 33 | 34 | /** 35 | * @constructor 36 | * @param {Object} options - options 37 | * @param {String|Array} options.directory - directories to be loaded 38 | * @param {Object} options.target - attach the target object from loaded files 39 | * @param {String} options.match - match the files when load, support glob, default to all js files 40 | * @param {String} options.ignore - ignore the files when load, support glob 41 | * @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path` 42 | * @param {Boolean} options.call - determine whether invoke when exports is function 43 | * @param {Boolean} options.override - determine whether override the property when get the same name 44 | * @param {Object} options.inject - an object that be the argument when invoke the function 45 | * @param {Function} options.filter - a function that filter the exports which can be loaded 46 | * @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list. 47 | */ 48 | constructor(options) { 49 | assert(options.directory, 'options.directory is required'); 50 | assert(options.target, 'options.target is required'); 51 | this.options = Object.assign({}, defaults, options); 52 | 53 | // compatible old options _lowercaseFirst_ 54 | if (this.options.lowercaseFirst === true) { 55 | deprecate('lowercaseFirst is deprecated, use caseStyle instead'); 56 | this.options.caseStyle = 'lower'; 57 | } 58 | } 59 | 60 | /** 61 | * attach items to target object. Mapping the directory to properties. 62 | * `app/controller/group/repository.js` => `target.group.repository` 63 | * @return {Object} target 64 | * @since 1.0.0 65 | */ 66 | load() { 67 | const items = this.parse(); 68 | const target = this.options.target; 69 | for (const item of items) { 70 | debug('loading item %j', item); 71 | // item { properties: [ 'a', 'b', 'c'], exports } 72 | // => target.a.b.c = exports 73 | item.properties.reduce((target, property, index) => { 74 | let obj; 75 | const properties = item.properties.slice(0, index + 1).join('.'); 76 | if (index === item.properties.length - 1) { 77 | if (property in target) { 78 | if (!this.options.override) throw new Error(`can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`); 79 | } 80 | obj = item.exports; 81 | if (obj && !is.primitive(obj)) { 82 | obj[FULLPATH] = item.fullpath; 83 | obj[EXPORTS] = true; 84 | } 85 | } else { 86 | obj = target[property] || {}; 87 | } 88 | target[property] = obj; 89 | debug('loaded %s', properties); 90 | return obj; 91 | }, target); 92 | } 93 | return target; 94 | } 95 | 96 | /** 97 | * Parse files from given directories, then return an items list, each item contains properties and exports. 98 | * 99 | * For example, parse `app/controller/group/repository.js` 100 | * 101 | * ``` 102 | * module.exports = app => { 103 | * return class RepositoryController extends app.Controller {}; 104 | * } 105 | * ``` 106 | * 107 | * It returns a item 108 | * 109 | * ``` 110 | * { 111 | * properties: [ 'group', 'repository' ], 112 | * exports: app => { ... }, 113 | * } 114 | * ``` 115 | * 116 | * `Properties` is an array that contains the directory of a filepath. 117 | * 118 | * `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing. 119 | * @return {Array} items 120 | * @since 1.0.0 121 | */ 122 | parse() { 123 | let files = this.options.match; 124 | if (!files) { 125 | files = (process.env.EGG_TYPESCRIPT === 'true' && utils.extensions['.ts']) 126 | ? [ '**/*.(js|ts)', '!**/*.d.ts' ] 127 | : [ '**/*.js' ]; 128 | } else { 129 | files = Array.isArray(files) ? files : [ files ]; 130 | } 131 | 132 | let ignore = this.options.ignore; 133 | if (ignore) { 134 | ignore = Array.isArray(ignore) ? ignore : [ ignore ]; 135 | ignore = ignore.filter(f => !!f).map(f => '!' + f); 136 | files = files.concat(ignore); 137 | } 138 | 139 | let directories = this.options.directory; 140 | if (!Array.isArray(directories)) { 141 | directories = [ directories ]; 142 | } 143 | 144 | const filter = is.function(this.options.filter) ? this.options.filter : null; 145 | const items = []; 146 | debug('parsing %j', directories); 147 | for (const directory of directories) { 148 | const filepaths = globby.sync(files, { cwd: directory }); 149 | for (const filepath of filepaths) { 150 | const fullpath = path.join(directory, filepath); 151 | if (!fs.statSync(fullpath).isFile()) continue; 152 | // get properties 153 | // app/service/foo/bar.js => [ 'foo', 'bar' ] 154 | const properties = getProperties(filepath, this.options); 155 | // app/service/foo/bar.js => service.foo.bar 156 | const pathName = directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.'); 157 | // get exports from the file 158 | const exports = getExports(fullpath, this.options, pathName); 159 | 160 | // ignore exports when it's null or false returned by filter function 161 | if (exports == null || (filter && filter(exports) === false)) continue; 162 | 163 | // set properties of class 164 | if (is.class(exports)) { 165 | exports.prototype.pathName = pathName; 166 | exports.prototype.fullPath = fullpath; 167 | } 168 | 169 | items.push({ fullpath, properties, exports }); 170 | debug('parse %s, properties %j, export %j', fullpath, properties, exports); 171 | } 172 | } 173 | 174 | return items; 175 | } 176 | 177 | } 178 | 179 | module.exports = FileLoader; 180 | module.exports.EXPORTS = EXPORTS; 181 | module.exports.FULLPATH = FULLPATH; 182 | 183 | // convert file path to an array of properties 184 | // a/b/c.js => ['a', 'b', 'c'] 185 | function getProperties(filepath, { caseStyle }) { 186 | // if caseStyle is function, return the result of function 187 | if (is.function(caseStyle)) { 188 | const result = caseStyle(filepath); 189 | assert(is.array(result), `caseStyle expect an array, but got ${result}`); 190 | return result; 191 | } 192 | // use default camelize 193 | return defaultCamelize(filepath, caseStyle); 194 | } 195 | 196 | // Get exports from filepath 197 | // If exports is null/undefined, it will be ignored 198 | function getExports(fullpath, { initializer, call, inject }, pathName) { 199 | let exports = utils.loadFile(fullpath); 200 | // process exports as you like 201 | if (initializer) { 202 | exports = initializer(exports, { path: fullpath, pathName }); 203 | } 204 | 205 | // return exports when it's a class or generator 206 | // 207 | // module.exports = class Service {}; 208 | // or 209 | // module.exports = function*() {} 210 | if (is.class(exports) || is.generatorFunction(exports) || is.asyncFunction(exports)) { 211 | return exports; 212 | } 213 | 214 | // return exports after call when it's a function 215 | // 216 | // module.exports = function(app) { 217 | // return {}; 218 | // } 219 | if (call && is.function(exports)) { 220 | exports = exports(inject); 221 | if (exports != null) { 222 | return exports; 223 | } 224 | } 225 | 226 | // return exports what is 227 | return exports; 228 | } 229 | 230 | function defaultCamelize(filepath, caseStyle) { 231 | const properties = filepath.substring(0, filepath.lastIndexOf('.')).split('/'); 232 | return properties.map(property => { 233 | if (!/^[a-z][a-z0-9_-]*$/i.test(property)) { 234 | throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`); 235 | } 236 | 237 | // use default camelize, will capitalize the first letter 238 | // foo_bar.js > FooBar 239 | // fooBar.js > FooBar 240 | // FooBar.js > FooBar 241 | // FooBar.js > FooBar 242 | // FooBar.js > fooBar (if lowercaseFirst is true) 243 | property = property.replace(/[_-][a-z]/ig, s => s.substring(1).toUpperCase()); 244 | let first = property[0]; 245 | switch (caseStyle) { 246 | case 'lower': 247 | first = first.toLowerCase(); 248 | break; 249 | case 'upper': 250 | first = first.toUpperCase(); 251 | break; 252 | case 'camel': 253 | default: 254 | } 255 | return first + property.substring(1); 256 | }); 257 | } 258 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('egg-core:config'); 4 | const path = require('path'); 5 | const extend = require('extend2'); 6 | const assert = require('assert'); 7 | 8 | 9 | const SET_CONFIG_META = Symbol('Loader#setConfigMeta'); 10 | 11 | module.exports = { 12 | 13 | /** 14 | * Load config/config.js 15 | * 16 | * Will merge config.default.js 和 config.${env}.js 17 | * 18 | * @method EggLoader#loadConfig 19 | * @since 1.0.0 20 | */ 21 | loadConfig() { 22 | this.timing.start('Load Config'); 23 | this.configMeta = {}; 24 | 25 | const target = {}; 26 | 27 | // Load Application config first 28 | const appConfig = this._preloadAppConfig(); 29 | 30 | // plugin config.default 31 | // framework config.default 32 | // app config.default 33 | // plugin config.{env} 34 | // framework config.{env} 35 | // app config.{env} 36 | for (const filename of this.getTypeFiles('config')) { 37 | for (const unit of this.getLoadUnits()) { 38 | const isApp = unit.type === 'app'; 39 | const config = this._loadConfig(unit.path, filename, isApp ? undefined : appConfig, unit.type); 40 | 41 | if (!config) { 42 | continue; 43 | } 44 | 45 | debug('Loaded config %s/%s, %j', unit.path, filename, config); 46 | extend(true, target, config); 47 | } 48 | } 49 | 50 | // You can manipulate the order of app.config.coreMiddleware and app.config.appMiddleware in app.js 51 | target.coreMiddleware = target.coreMiddlewares = target.coreMiddleware || []; 52 | target.appMiddleware = target.appMiddlewares = target.middleware || []; 53 | 54 | this.config = target; 55 | this.timing.end('Load Config'); 56 | }, 57 | 58 | _preloadAppConfig() { 59 | const names = [ 60 | 'config.default', 61 | `config.${this.serverEnv}`, 62 | ]; 63 | const target = {}; 64 | for (const filename of names) { 65 | const config = this._loadConfig(this.options.baseDir, filename, undefined, 'app'); 66 | extend(true, target, config); 67 | } 68 | return target; 69 | }, 70 | 71 | _loadConfig(dirpath, filename, extraInject, type) { 72 | const isPlugin = type === 'plugin'; 73 | const isApp = type === 'app'; 74 | 75 | let filepath = this.resolveModule(path.join(dirpath, 'config', filename)); 76 | // let config.js compatible 77 | if (filename === 'config.default' && !filepath) { 78 | filepath = this.resolveModule(path.join(dirpath, 'config/config')); 79 | } 80 | const config = this.loadFile(filepath, this.appInfo, extraInject); 81 | 82 | if (!config) return null; 83 | 84 | if (isPlugin || isApp) { 85 | assert(!config.coreMiddleware, 'Can not define coreMiddleware in app or plugin'); 86 | } 87 | if (!isApp) { 88 | assert(!config.middleware, 'Can not define middleware in ' + filepath); 89 | } 90 | 91 | // store config meta, check where is the property of config come from. 92 | this[SET_CONFIG_META](config, filepath); 93 | 94 | return config; 95 | }, 96 | 97 | [SET_CONFIG_META](config, filepath) { 98 | config = extend(true, {}, config); 99 | setConfig(config, filepath); 100 | extend(true, this.configMeta, config); 101 | }, 102 | }; 103 | 104 | function setConfig(obj, filepath) { 105 | for (const key of Object.keys(obj)) { 106 | const val = obj[key]; 107 | if (val && Object.getPrototypeOf(val) === Object.prototype && Object.keys(val).length > 0) { 108 | setConfig(val, filepath); 109 | continue; 110 | } 111 | obj[key] = filepath; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const is = require('is-type-of'); 5 | const utility = require('utility'); 6 | const utils = require('../../utils'); 7 | const FULLPATH = require('../file_loader').FULLPATH; 8 | 9 | 10 | module.exports = { 11 | 12 | /** 13 | * Load app/controller 14 | * @param {Object} opt - LoaderOptions 15 | * @since 1.0.0 16 | */ 17 | loadController(opt) { 18 | this.timing.start('Load Controller'); 19 | opt = Object.assign({ 20 | caseStyle: 'lower', 21 | directory: path.join(this.options.baseDir, 'app/controller'), 22 | initializer: (obj, opt) => { 23 | // return class if it exports a function 24 | // ```js 25 | // module.exports = app => { 26 | // return class HomeController extends app.Controller {}; 27 | // } 28 | // ``` 29 | if (is.function(obj) && !is.generatorFunction(obj) && !is.class(obj) && !is.asyncFunction(obj)) { 30 | obj = obj(this.app); 31 | } 32 | if (is.class(obj)) { 33 | obj.prototype.pathName = opt.pathName; 34 | obj.prototype.fullPath = opt.path; 35 | return wrapClass(obj); 36 | } 37 | if (is.object(obj)) { 38 | return wrapObject(obj, opt.path); 39 | } 40 | // support generatorFunction for forward compatbility 41 | if (is.generatorFunction(obj) || is.asyncFunction(obj)) { 42 | return wrapObject({ 'module.exports': obj }, opt.path)['module.exports']; 43 | } 44 | return obj; 45 | }, 46 | }, opt); 47 | const controllerBase = opt.directory; 48 | 49 | this.loadToApp(controllerBase, 'controller', opt); 50 | this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase); 51 | this.timing.end('Load Controller'); 52 | }, 53 | 54 | }; 55 | 56 | // wrap the class, yield a object with middlewares 57 | function wrapClass(Controller) { 58 | let proto = Controller.prototype; 59 | const ret = {}; 60 | // tracing the prototype chain 61 | while (proto !== Object.prototype) { 62 | const keys = Object.getOwnPropertyNames(proto); 63 | for (const key of keys) { 64 | // getOwnPropertyNames will return constructor 65 | // that should be ignored 66 | if (key === 'constructor') { 67 | continue; 68 | } 69 | // skip getter, setter & non-function properties 70 | const d = Object.getOwnPropertyDescriptor(proto, key); 71 | // prevent to override sub method 72 | if (is.function(d.value) && !ret.hasOwnProperty(key)) { 73 | ret[key] = methodToMiddleware(Controller, key); 74 | ret[key][FULLPATH] = Controller.prototype.fullPath + '#' + Controller.name + '.' + key + '()'; 75 | } 76 | } 77 | proto = Object.getPrototypeOf(proto); 78 | } 79 | return ret; 80 | 81 | function methodToMiddleware(Controller, key) { 82 | return function classControllerMiddleware(...args) { 83 | const controller = new Controller(this); 84 | if (!this.app.config.controller || !this.app.config.controller.supportParams) { 85 | args = [ this ]; 86 | } 87 | return utils.callFn(controller[key], args, controller); 88 | }; 89 | } 90 | } 91 | 92 | // wrap the method of the object, method can receive ctx as it's first argument 93 | function wrapObject(obj, path, prefix) { 94 | const keys = Object.keys(obj); 95 | const ret = {}; 96 | for (const key of keys) { 97 | if (is.function(obj[key])) { 98 | const names = utility.getParamNames(obj[key]); 99 | if (names[0] === 'next') { 100 | throw new Error(`controller \`${prefix || ''}${key}\` should not use next as argument from file ${path}`); 101 | } 102 | ret[key] = functionToMiddleware(obj[key]); 103 | ret[key][FULLPATH] = `${path}#${prefix || ''}${key}()`; 104 | } else if (is.object(obj[key])) { 105 | ret[key] = wrapObject(obj[key], path, `${prefix || ''}${key}.`); 106 | } 107 | } 108 | return ret; 109 | 110 | function functionToMiddleware(func) { 111 | const objectControllerMiddleware = async function(...args) { 112 | if (!this.app.config.controller || !this.app.config.controller.supportParams) { 113 | args = [ this ]; 114 | } 115 | return await utils.callFn(func, args, this); 116 | }; 117 | for (const key in func) { 118 | objectControllerMiddleware[key] = func[key]; 119 | } 120 | return objectControllerMiddleware; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/custom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const is = require('is-type-of'); 4 | const path = require('path'); 5 | 6 | const LOAD_BOOT_HOOK = Symbol('Loader#loadBootHook'); 7 | 8 | module.exports = { 9 | 10 | /** 11 | * load app.js 12 | * 13 | * @example 14 | * - old: 15 | * 16 | * ```js 17 | * module.exports = function(app) { 18 | * doSomething(); 19 | * } 20 | * ``` 21 | * 22 | * - new: 23 | * 24 | * ```js 25 | * module.exports = class Boot { 26 | * constructor(app) { 27 | * this.app = app; 28 | * } 29 | * configDidLoad() { 30 | * doSomething(); 31 | * } 32 | * } 33 | * @since 1.0.0 34 | */ 35 | loadCustomApp() { 36 | this[LOAD_BOOT_HOOK]('app'); 37 | this.lifecycle.triggerConfigWillLoad(); 38 | }, 39 | 40 | /** 41 | * Load agent.js, same as {@link EggLoader#loadCustomApp} 42 | */ 43 | loadCustomAgent() { 44 | this[LOAD_BOOT_HOOK]('agent'); 45 | this.lifecycle.triggerConfigWillLoad(); 46 | }, 47 | 48 | // FIXME: no logger used after egg removed 49 | loadBootHook() { 50 | // do nothing 51 | }, 52 | 53 | [LOAD_BOOT_HOOK](fileName) { 54 | this.timing.start(`Load ${fileName}.js`); 55 | for (const unit of this.getLoadUnits()) { 56 | const bootFilePath = this.resolveModule(path.join(unit.path, fileName)); 57 | if (!bootFilePath) { 58 | continue; 59 | } 60 | const bootHook = this.requireFile(bootFilePath); 61 | if (is.class(bootHook)) { 62 | // if is boot class, add to lifecycle 63 | this.lifecycle.addBootHook(bootHook); 64 | } else if (is.function(bootHook)) { 65 | // if is boot function, wrap to class 66 | // for compatibility 67 | this.lifecycle.addFunctionAsBootHook(bootHook); 68 | } else { 69 | this.options.logger.warn('[egg-loader] %s must exports a boot class', bootFilePath); 70 | } 71 | } 72 | // init boots 73 | this.lifecycle.init(); 74 | this.timing.end(`Load ${fileName}.js`); 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('egg-core:extend'); 4 | const deprecate = require('depd')('egg'); 5 | const path = require('path'); 6 | 7 | const originalPrototypes = { 8 | request: require('koa/lib/request'), 9 | response: require('koa/lib/response'), 10 | context: require('koa/lib/context'), 11 | application: require('koa/lib/application'), 12 | }; 13 | 14 | module.exports = { 15 | 16 | /** 17 | * mixin Agent.prototype 18 | * @method EggLoader#loadAgentExtend 19 | * @since 1.0.0 20 | */ 21 | loadAgentExtend() { 22 | this.loadExtend('agent', this.app); 23 | }, 24 | 25 | /** 26 | * mixin Application.prototype 27 | * @method EggLoader#loadApplicationExtend 28 | * @since 1.0.0 29 | */ 30 | loadApplicationExtend() { 31 | this.loadExtend('application', this.app); 32 | }, 33 | 34 | /** 35 | * mixin Request.prototype 36 | * @method EggLoader#loadRequestExtend 37 | * @since 1.0.0 38 | */ 39 | loadRequestExtend() { 40 | this.loadExtend('request', this.app.request); 41 | }, 42 | 43 | /** 44 | * mixin Response.prototype 45 | * @method EggLoader#loadResponseExtend 46 | * @since 1.0.0 47 | */ 48 | loadResponseExtend() { 49 | this.loadExtend('response', this.app.response); 50 | }, 51 | 52 | /** 53 | * mixin Context.prototype 54 | * @method EggLoader#loadContextExtend 55 | * @since 1.0.0 56 | */ 57 | loadContextExtend() { 58 | this.loadExtend('context', this.app.context); 59 | }, 60 | 61 | /** 62 | * mixin app.Helper.prototype 63 | * @method EggLoader#loadHelperExtend 64 | * @since 1.0.0 65 | */ 66 | loadHelperExtend() { 67 | if (this.app && this.app.Helper) { 68 | this.loadExtend('helper', this.app.Helper.prototype); 69 | } 70 | }, 71 | 72 | /** 73 | * Find all extend file paths by name 74 | * can be override in top level framework to support load `app/extends/{name}.js` 75 | * 76 | * @param {String} name - filename which may be `app/extend/{name}.js` 77 | * @return {Array} filepaths extend file paths 78 | * @private 79 | */ 80 | getExtendFilePaths(name) { 81 | return this.getLoadUnits().map(unit => path.join(unit.path, 'app/extend', name)); 82 | }, 83 | 84 | /** 85 | * Loader app/extend/xx.js to `prototype`, 86 | * @method loadExtend 87 | * @param {String} name - filename which may be `app/extend/{name}.js` 88 | * @param {Object} proto - prototype that mixed 89 | * @since 1.0.0 90 | */ 91 | loadExtend(name, proto) { 92 | this.timing.start(`Load extend/${name}.js`); 93 | // All extend files 94 | const filepaths = this.getExtendFilePaths(name); 95 | // if use mm.env and serverEnv is not unittest 96 | const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest'; 97 | for (let i = 0, l = filepaths.length; i < l; i++) { 98 | const filepath = filepaths[i]; 99 | filepaths.push(filepath + `.${this.serverEnv}`); 100 | if (isAddUnittest) filepaths.push(filepath + '.unittest'); 101 | } 102 | 103 | const mergeRecord = new Map(); 104 | for (let filepath of filepaths) { 105 | filepath = this.resolveModule(filepath); 106 | if (!filepath) { 107 | continue; 108 | } else if (filepath.endsWith('/index.js')) { 109 | // TODO: remove support at next version 110 | deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`); 111 | } 112 | 113 | const ext = this.requireFile(filepath); 114 | 115 | const properties = Object.getOwnPropertyNames(ext) 116 | .concat(Object.getOwnPropertySymbols(ext)); 117 | 118 | for (const property of properties) { 119 | if (mergeRecord.has(property)) { 120 | debug('Property: "%s" already exists in "%s",it will be redefined by "%s"', 121 | property, mergeRecord.get(property), filepath); 122 | } 123 | 124 | // Copy descriptor 125 | let descriptor = Object.getOwnPropertyDescriptor(ext, property); 126 | let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property); 127 | if (!originalDescriptor) { 128 | // try to get descriptor from originalPrototypes 129 | const originalProto = originalPrototypes[name]; 130 | if (originalProto) { 131 | originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property); 132 | } 133 | } 134 | if (originalDescriptor) { 135 | // don't override descriptor 136 | descriptor = Object.assign({}, descriptor); 137 | if (!descriptor.set && originalDescriptor.set) { 138 | descriptor.set = originalDescriptor.set; 139 | } 140 | if (!descriptor.get && originalDescriptor.get) { 141 | descriptor.get = originalDescriptor.get; 142 | } 143 | } 144 | Object.defineProperty(proto, property, descriptor); 145 | mergeRecord.set(property, filepath); 146 | } 147 | debug('merge %j to %s from %s', Object.keys(ext), name, filepath); 148 | } 149 | this.timing.end(`Load extend/${name}.js`); 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const join = require('path').join; 4 | const is = require('is-type-of'); 5 | const inspect = require('util').inspect; 6 | const assert = require('assert'); 7 | const debug = require('debug')('egg-core:middleware'); 8 | const pathMatching = require('egg-path-matching'); 9 | const utils = require('../../utils'); 10 | 11 | 12 | module.exports = { 13 | 14 | /** 15 | * Load app/middleware 16 | * 17 | * app.config.xx is the options of the middleware xx that has same name as config 18 | * 19 | * @method EggLoader#loadMiddleware 20 | * @param {Object} opt - LoaderOptions 21 | * @example 22 | * ```js 23 | * // app/middleware/status.js 24 | * module.exports = function(options, app) { 25 | * // options == app.config.status 26 | * return function*(next) { 27 | * yield next; 28 | * } 29 | * } 30 | * ``` 31 | * @since 1.0.0 32 | */ 33 | loadMiddleware(opt) { 34 | this.timing.start('Load Middleware'); 35 | const app = this.app; 36 | 37 | // load middleware to app.middleware 38 | opt = Object.assign({ 39 | call: false, 40 | override: true, 41 | caseStyle: 'lower', 42 | directory: this.getLoadUnits().map(unit => join(unit.path, 'app/middleware')), 43 | }, opt); 44 | const middlewarePaths = opt.directory; 45 | this.loadToApp(middlewarePaths, 'middlewares', opt); 46 | 47 | for (const name in app.middlewares) { 48 | Object.defineProperty(app.middleware, name, { 49 | get() { 50 | return app.middlewares[name]; 51 | }, 52 | enumerable: false, 53 | configurable: false, 54 | }); 55 | } 56 | 57 | this.options.logger.info('Use coreMiddleware order: %j', this.config.coreMiddleware); 58 | this.options.logger.info('Use appMiddleware order: %j', this.config.appMiddleware); 59 | 60 | // use middleware ordered by app.config.coreMiddleware and app.config.appMiddleware 61 | const middlewareNames = this.config.coreMiddleware.concat(this.config.appMiddleware); 62 | debug('middlewareNames: %j', middlewareNames); 63 | const middlewaresMap = new Map(); 64 | for (const name of middlewareNames) { 65 | if (!app.middlewares[name]) { 66 | throw new TypeError(`Middleware ${name} not found`); 67 | } 68 | if (middlewaresMap.has(name)) { 69 | throw new TypeError(`Middleware ${name} redefined`); 70 | } 71 | middlewaresMap.set(name, true); 72 | 73 | const options = this.config[name] || {}; 74 | let mw = app.middlewares[name]; 75 | mw = mw(options, app); 76 | assert(is.function(mw), `Middleware ${name} must be a function, but actual is ${inspect(mw)}`); 77 | mw._name = name; 78 | // middlewares support options.enable, options.ignore and options.match 79 | mw = wrapMiddleware(mw, options); 80 | if (mw) { 81 | app.use(mw); 82 | debug('Use middleware: %s with options: %j', name, options); 83 | this.options.logger.info('[egg:loader] Use middleware: %s', name); 84 | } else { 85 | this.options.logger.info('[egg:loader] Disable middleware: %s', name); 86 | } 87 | } 88 | 89 | this.options.logger.info('[egg:loader] Loaded middleware from %j', middlewarePaths); 90 | this.timing.end('Load Middleware'); 91 | }, 92 | 93 | }; 94 | 95 | function wrapMiddleware(mw, options) { 96 | // support options.enable 97 | if (options.enable === false) return null; 98 | 99 | // support generator function 100 | mw = utils.middleware(mw); 101 | 102 | // support options.match and options.ignore 103 | if (!options.match && !options.ignore) return mw; 104 | const match = pathMatching(options); 105 | 106 | const fn = (ctx, next) => { 107 | if (!match(ctx)) return next(); 108 | return mw(ctx, next); 109 | }; 110 | fn._name = mw._name + 'middlewareWrapper'; 111 | return fn; 112 | } 113 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | 6 | module.exports = { 7 | 8 | /** 9 | * Load app/router.js 10 | * @method EggLoader#loadRouter 11 | * @param {Object} opt - LoaderOptions 12 | * @since 1.0.0 13 | */ 14 | loadRouter() { 15 | this.timing.start('Load Router'); 16 | // 加载 router.js 17 | this.loadFile(path.join(this.options.baseDir, 'app/router')); 18 | this.timing.end('Load Router'); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/loader/mixin/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | 6 | module.exports = { 7 | 8 | /** 9 | * Load app/service 10 | * @method EggLoader#loadService 11 | * @param {Object} opt - LoaderOptions 12 | * @since 1.0.0 13 | */ 14 | loadService(opt) { 15 | this.timing.start('Load Service'); 16 | // 载入到 app.serviceClasses 17 | opt = Object.assign({ 18 | call: true, 19 | caseStyle: 'lower', 20 | fieldClass: 'serviceClasses', 21 | directory: this.getLoadUnits().map(unit => path.join(unit.path, 'app/service')), 22 | }, opt); 23 | const servicePaths = opt.directory; 24 | this.loadToContext(servicePaths, 'service', opt); 25 | this.timing.end('Load Service'); 26 | }, 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/utils/base_context_class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * BaseContextClass is a base class that can be extended, 5 | * it's instantiated in context level, 6 | * {@link Helper}, {@link Service} is extending it. 7 | */ 8 | class BaseContextClass { 9 | 10 | /** 11 | * @constructor 12 | * @param {Context} ctx - context instance 13 | * @since 1.0.0 14 | */ 15 | constructor(ctx) { 16 | /** 17 | * @member {Context} BaseContextClass#ctx 18 | * @since 1.0.0 19 | */ 20 | this.ctx = ctx; 21 | /** 22 | * @member {Application} BaseContextClass#app 23 | * @since 1.0.0 24 | */ 25 | this.app = ctx.app; 26 | /** 27 | * @member {Config} BaseContextClass#config 28 | * @since 1.0.0 29 | */ 30 | this.config = ctx.app.config; 31 | /** 32 | * @member {Service} BaseContextClass#service 33 | * @since 1.0.0 34 | */ 35 | this.service = ctx.service; 36 | } 37 | } 38 | 39 | module.exports = BaseContextClass; 40 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const convert = require('koa-convert'); 4 | const is = require('is-type-of'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const co = require('co'); 8 | const BuiltinModule = require('module'); 9 | 10 | // Guard against poorly mocked module constructors. 11 | const Module = module.constructor.length > 1 12 | ? module.constructor 13 | /* istanbul ignore next */ 14 | : BuiltinModule; 15 | 16 | module.exports = { 17 | extensions: Module._extensions, 18 | 19 | loadFile(filepath) { 20 | try { 21 | // if not js module, just return content buffer 22 | const extname = path.extname(filepath); 23 | if (extname && !Module._extensions[extname]) { 24 | return fs.readFileSync(filepath); 25 | } 26 | // require js module 27 | const obj = require(filepath); 28 | if (!obj) return obj; 29 | // it's es module 30 | if (obj.__esModule) return 'default' in obj ? obj.default : obj; 31 | return obj; 32 | } catch (err) { 33 | err.message = `[egg-core] load file: ${filepath}, error: ${err.message}`; 34 | throw err; 35 | } 36 | }, 37 | 38 | methods: [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete' ], 39 | 40 | async callFn(fn, args, ctx) { 41 | args = args || []; 42 | if (!is.function(fn)) return; 43 | if (is.generatorFunction(fn)) fn = co.wrap(fn); 44 | return ctx ? fn.call(ctx, ...args) : fn(...args); 45 | }, 46 | 47 | middleware(fn) { 48 | return is.generatorFunction(fn) ? convert(fn) : fn; 49 | }, 50 | 51 | getCalleeFromStack(withLine, stackIndex) { 52 | stackIndex = stackIndex === undefined ? 2 : stackIndex; 53 | const limit = Error.stackTraceLimit; 54 | const prep = Error.prepareStackTrace; 55 | 56 | Error.prepareStackTrace = prepareObjectStackTrace; 57 | Error.stackTraceLimit = 5; 58 | 59 | // capture the stack 60 | const obj = {}; 61 | Error.captureStackTrace(obj); 62 | let callSite = obj.stack[stackIndex]; 63 | let fileName; 64 | /* istanbul ignore else */ 65 | if (callSite) { 66 | // egg-mock will create a proxy 67 | // https://github.com/eggjs/egg-mock/blob/master/lib/app.js#L174 68 | fileName = callSite.getFileName(); 69 | /* istanbul ignore if */ 70 | if (fileName && fileName.endsWith('egg-mock/lib/app.js')) { 71 | // TODO: add test 72 | callSite = obj.stack[stackIndex + 1]; 73 | fileName = callSite.getFileName(); 74 | } 75 | } 76 | 77 | Error.prepareStackTrace = prep; 78 | Error.stackTraceLimit = limit; 79 | 80 | /* istanbul ignore if */ 81 | if (!callSite || !fileName) return ''; 82 | if (!withLine) return fileName; 83 | return `${fileName}:${callSite.getLineNumber()}:${callSite.getColumnNumber()}`; 84 | }, 85 | 86 | getResolvedFilename(filepath, baseDir) { 87 | const reg = /[/\\]/g; 88 | return filepath.replace(baseDir + path.sep, '').replace(reg, '/'); 89 | }, 90 | }; 91 | 92 | 93 | /** 94 | * Capture call site stack from v8. 95 | * https://github.com/v8/v8/wiki/Stack-Trace-API 96 | */ 97 | 98 | function prepareObjectStackTrace(obj, stack) { 99 | return stack; 100 | } 101 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/utils/sequencify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('egg-core#sequencify'); 4 | 5 | function sequence(tasks, names, results, missing, recursive, nest, optional, parent) { 6 | names.forEach(function(name) { 7 | if (results.requires[name]) return; 8 | 9 | const node = tasks[name]; 10 | 11 | if (!node) { 12 | if (optional === true) return; 13 | missing.push(name); 14 | } else if (nest.includes(name)) { 15 | nest.push(name); 16 | recursive.push(nest.slice(0)); 17 | nest.pop(name); 18 | } else if (node.dependencies.length || node.optionalDependencies.length) { 19 | nest.push(name); 20 | if (node.dependencies.length) { 21 | sequence(tasks, node.dependencies, results, missing, recursive, nest, optional, name); 22 | } 23 | if (node.optionalDependencies.length) { 24 | sequence(tasks, node.optionalDependencies, results, missing, recursive, nest, true, name); 25 | } 26 | nest.pop(name); 27 | } 28 | if (!optional) { 29 | results.requires[name] = true; 30 | debug('task: %s is enabled by %s', name, parent); 31 | } 32 | if (!results.sequence.includes(name)) { 33 | results.sequence.push(name); 34 | } 35 | }); 36 | } 37 | 38 | // tasks: object with keys as task names 39 | // names: array of task names 40 | module.exports = function(tasks, names) { 41 | const results = { 42 | sequence: [], 43 | requires: {}, 44 | }; // the final sequence 45 | const missing = []; // missing tasks 46 | const recursive = []; // recursive task dependencies 47 | 48 | sequence(tasks, names, results, missing, recursive, [], false, 'app'); 49 | 50 | if (missing.length || recursive.length) { 51 | results.sequence = []; // results are incomplete at best, completely wrong at worst, remove them to avoid confusion 52 | } 53 | 54 | return { 55 | sequence: results.sequence.filter(item => results.requires[item]), 56 | missingTasks: missing, 57 | recursiveDependencies: recursive, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-core/lib/utils/timing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const MAP = Symbol('Timing#map'); 5 | const LIST = Symbol('Timing#list'); 6 | 7 | 8 | class Timing { 9 | 10 | constructor() { 11 | this[MAP] = new Map(); 12 | this[LIST] = []; 13 | } 14 | 15 | start(name) { 16 | if (!name) return; 17 | 18 | if (this[MAP].has(name)) this.end(name); 19 | 20 | const start = Date.now(); 21 | const item = { 22 | name, 23 | start, 24 | end: undefined, 25 | duration: undefined, 26 | pid: process.pid, 27 | index: this[LIST].length, 28 | }; 29 | this[MAP].set(name, item); 30 | this[LIST].push(item); 31 | return item; 32 | } 33 | 34 | end(name) { 35 | if (!name) return; 36 | assert(this[MAP].has(name), `should run timing.start('${name}') first`); 37 | 38 | const item = this[MAP].get(name); 39 | item.end = Date.now(); 40 | item.duration = item.end - item.start; 41 | return item; 42 | } 43 | 44 | toJSON() { 45 | return this[LIST]; 46 | } 47 | } 48 | 49 | module.exports = Timing; 50 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-router/check.ts: -------------------------------------------------------------------------------- 1 | import { KoaRouter, EggRouter } from './'; 2 | 3 | const router = new KoaRouter({ prefix: '/' }); 4 | router.redirect('/login', 'sign-in', 301); 5 | router.route('route'); 6 | router.stack.slice(0); 7 | router.param('user', () => {}); 8 | router.prefix('/test/').route('route'); 9 | router.use('/test', () => {}).use('/ttt', () => {}); 10 | 11 | const eggRouter = new EggRouter({}, {}); 12 | eggRouter.redirect('/login', 'sign-in', 301); 13 | eggRouter.route('route'); 14 | eggRouter.stack.slice(0); 15 | eggRouter.param('user', () => {}); 16 | eggRouter.prefix('/test/').route('route'); 17 | eggRouter.use('/test', () => {}).use('/ttt', () => {}); 18 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-router/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize a new routing Layer with given `method`, `path`, and `middleware`. 3 | * 4 | * @param {String|RegExp} path Path string or regular expression. 5 | * @param {Array} methods Array of HTTP verbs. 6 | * @param {Array} middleware Layer callback/middleware or series of. 7 | * @param {Object=} opts 8 | * @param {String=} opts.name route name 9 | * @param {String=} opts.sensitive case sensitive (default: false) 10 | * @param {String=} opts.strict require the trailing slash (default: false) 11 | * @returns {Layer} 12 | * @private 13 | */ 14 | declare class Layer { 15 | constructor(path: string | RegExp, methods: any[], middleware: any[], opts: any); 16 | opts: any; 17 | name: any; 18 | methods: any; 19 | paramNames: any; 20 | stack: any; 21 | path: any; 22 | regexp: any; 23 | } 24 | /** 25 | * Create a new router. 26 | * 27 | * @example 28 | * 29 | * Basic usage: 30 | * 31 | * ```javascript 32 | * var Koa = require('koa'); 33 | * var Router = require('koa-router'); 34 | * 35 | * var app = new Koa(); 36 | * var router = new Router(); 37 | * 38 | * router.get('/', (ctx, next) => { 39 | * // ctx.router available 40 | * }); 41 | * 42 | * app 43 | * .use(router.routes()) 44 | * .use(router.allowedMethods()); 45 | * ``` 46 | * 47 | * @alias module:koa-router 48 | * @param {Object} [opts] 49 | * @param {String} [opts.prefix] prefix router paths 50 | * @constructor 51 | */ 52 | declare class Router { 53 | constructor(opts: _EggRouter.T100); 54 | del: any; 55 | /** 56 | * Use given middleware. 57 | * 58 | * Middleware run in the order they are defined by `.use()`. They are invoked 59 | * sequentially, requests start at the first middleware and work their way 60 | * "down" the middleware stack. 61 | * 62 | * @example 63 | * 64 | * ```javascript 65 | * // session middleware will run before authorize 66 | * router 67 | * .use(session()) 68 | * .use(authorize()); 69 | * 70 | * // use middleware only with given path 71 | * router.use('/users', userAuth()); 72 | * 73 | * // or with an array of paths 74 | * router.use(['/users', '/admin'], userAuth()); 75 | * 76 | * app.use(router.routes()); 77 | * ``` 78 | * 79 | * @param {String=} path 80 | * @param {Function} middleware 81 | * @param {Function=} ... 82 | * @returns {Router} 83 | */ 84 | use(...args: any[]): Router; 85 | /** 86 | * Set the path prefix for a Router instance that was already initialized. 87 | * 88 | * @example 89 | * 90 | * ```javascript 91 | * router.prefix('/things/:thing_id') 92 | * ``` 93 | * 94 | * @param {String} prefix 95 | * @returns {Router} 96 | */ 97 | prefix(prefix: string): Router; 98 | /** 99 | * Returns router middleware which dispatches a route matching the request. 100 | * 101 | * @returns {Function} 102 | */ 103 | middleware(): (...args: any[]) => any; 104 | /** 105 | * Returns router middleware which dispatches a route matching the request. 106 | * 107 | * @returns {Function} 108 | */ 109 | routes(): (...args: any[]) => any; 110 | /** 111 | * Returns separate middleware for responding to `OPTIONS` requests with 112 | * an `Allow` header containing the allowed methods, as well as responding 113 | * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate. 114 | * 115 | * @example 116 | * 117 | * ```javascript 118 | * var Koa = require('koa'); 119 | * var Router = require('koa-router'); 120 | * 121 | * var app = new Koa(); 122 | * var router = new Router(); 123 | * 124 | * app.use(router.routes()); 125 | * app.use(router.allowedMethods()); 126 | * ``` 127 | * 128 | * **Example with [Boom](https://github.com/hapijs/boom)** 129 | * 130 | * ```javascript 131 | * var Koa = require('koa'); 132 | * var Router = require('koa-router'); 133 | * var Boom = require('boom'); 134 | * 135 | * var app = new Koa(); 136 | * var router = new Router(); 137 | * 138 | * app.use(router.routes()); 139 | * app.use(router.allowedMethods({ 140 | * throw: true, 141 | * notImplemented: () => new Boom.notImplemented(), 142 | * methodNotAllowed: () => new Boom.methodNotAllowed() 143 | * })); 144 | * ``` 145 | * 146 | * @param {Object=} options 147 | * @param {Boolean=} options.throw throw error instead of setting status and header 148 | * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error 149 | * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error 150 | * @returns {Function} 151 | */ 152 | allowedMethods(options?: any): (...args: any[]) => any; 153 | /** 154 | * Redirect `source` to `destination` URL with optional 30x status `code`. 155 | * 156 | * Both `source` and `destination` can be route names. 157 | * 158 | * ```javascript 159 | * router.redirect('/login', 'sign-in'); 160 | * ``` 161 | * 162 | * This is equivalent to: 163 | * 164 | * ```javascript 165 | * router.all('/login', ctx => { 166 | * ctx.redirect('/sign-in'); 167 | * ctx.status = 301; 168 | * }); 169 | * ``` 170 | * 171 | * @param {String} source URL or route name. 172 | * @param {String} destination URL or route name. 173 | * @param {Number=} code HTTP status code (default: 301). 174 | * @returns {Router} 175 | */ 176 | redirect(source: string, destination: string, code?: number): Router; 177 | /** 178 | * Lookup route with given `name`. 179 | * 180 | * @param {String} name 181 | * @returns {Layer|false} 182 | */ 183 | route(name: string): boolean | Layer; 184 | /** 185 | * Run middleware for named route parameters. Useful for auto-loading or 186 | * validation. 187 | * 188 | * @example 189 | * 190 | * ```javascript 191 | * router 192 | * .param('user', (id, ctx, next) => { 193 | * ctx.user = users[id]; 194 | * if (!ctx.user) return ctx.status = 404; 195 | * return next(); 196 | * }) 197 | * .get('/users/:user', ctx => { 198 | * ctx.body = ctx.user; 199 | * }) 200 | * .get('/users/:user/friends', ctx => { 201 | * return ctx.user.getFriends().then(function(friends) { 202 | * ctx.body = friends; 203 | * }); 204 | * }) 205 | * // /users/3 => {"id": 3, "name": "Alex"} 206 | * // /users/3/friends => [{"id": 4, "name": "TJ"}] 207 | * ``` 208 | * 209 | * @param {String} param 210 | * @param {Function} middleware 211 | * @returns {Router} 212 | */ 213 | param(param: string, middleware: (...args: any[]) => any): Router; 214 | /** 215 | * Generate URL from url pattern and given `params`. 216 | * 217 | * @example 218 | * 219 | * ```javascript 220 | * var url = Router.url('/users/:id', {id: 1}); 221 | * // => "/users/1" 222 | * ``` 223 | * 224 | * @param {String} path url pattern 225 | * @param {Object} params url parameters 226 | * @returns {String} 227 | */ 228 | static url(path: string, params: any, ...args: any[]): string; 229 | opts: _EggRouter.T100; 230 | methods: any; 231 | params: _EggRouter.T101; 232 | stack: any[]; 233 | } 234 | interface T102 { 235 | KoaRouter: typeof Router; 236 | EggRouter: typeof EggRouter; 237 | } 238 | /** 239 | * FIXME: move these patch into @eggjs/router 240 | */ 241 | declare class EggRouter extends Router { 242 | app: any; 243 | /** 244 | * @constructor 245 | * @param {Object} opts - Router options. 246 | * @param {Application} app - Application object. 247 | */ 248 | constructor(opts: any, app: any); 249 | patchRouterMethod(): void; 250 | /** 251 | * Create and register a route. 252 | * @param {String} path - url path 253 | * @param {Array} methods - Array of HTTP verbs 254 | * @param {Array} middlewares - 255 | * @param {Object} opts - 256 | * @return {Route} this 257 | */ 258 | register(path: string, methods: any[], middlewares: any[], opts: any): any; 259 | /** 260 | * restful router api 261 | * @param {String} name - Router name 262 | * @param {String} prefix - url prefix 263 | * @param {Function} middleware - middleware or controller 264 | * @example 265 | * ```js 266 | * app.resources('/posts', 'posts') 267 | * app.resources('posts', '/posts', 'posts') 268 | * app.resources('posts', '/posts', app.role.can('user'), app.controller.posts) 269 | * ``` 270 | * 271 | * Examples: 272 | * 273 | * ```js 274 | * app.resources('/posts', 'posts') 275 | * ``` 276 | * 277 | * yield router mapping 278 | * 279 | * Method | Path | Route Name | Controller.Action 280 | * -------|-----------------|----------------|----------------------------- 281 | * GET | /posts | posts | app.controller.posts.index 282 | * GET | /posts/new | new_post | app.controller.posts.new 283 | * GET | /posts/:id | post | app.controller.posts.show 284 | * GET | /posts/:id/edit | edit_post | app.controller.posts.edit 285 | * POST | /posts | posts | app.controller.posts.create 286 | * PATCH | /posts/:id | post | app.controller.posts.update 287 | * DELETE | /posts/:id | post | app.controller.posts.destroy 288 | * 289 | * app.router.url can generate url based on arguments 290 | * ```js 291 | * app.router.url('posts') 292 | * => /posts 293 | * app.router.url('post', { id: 1 }) 294 | * => /posts/1 295 | * app.router.url('new_post') 296 | * => /posts/new 297 | * app.router.url('edit_post', { id: 1 }) 298 | * => /posts/1/edit 299 | * ``` 300 | * @return {Router} return route object. 301 | * @since 1.0.0 302 | */ 303 | resources(...args: any[]): Router; 304 | /** 305 | * @param {String} name - Router name 306 | * @param {Object} params - more parameters 307 | * @example 308 | * ```js 309 | * router.url('edit_post', { id: 1, name: 'foo', page: 2 }) 310 | * => /posts/1/edit?name=foo&page=2 311 | * router.url('posts', { name: 'foo&1', page: 2 }) 312 | * => /posts?name=foo%261&page=2 313 | * ``` 314 | * @return {String} url by path name and query params. 315 | * @since 1.0.0 316 | */ 317 | url(name: string, params: any): string; 318 | pathFor(name: any, params: any): string; 319 | } 320 | declare const _EggRouter: typeof Router & T102; 321 | declare namespace _EggRouter { 322 | export interface T100 { 323 | prefix?: string; 324 | } 325 | export interface T101 { 326 | [key: string]: any; 327 | } 328 | } 329 | export = _EggRouter; 330 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-router/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const KoaRouter = require('./lib/router'); 4 | const EggRouter = require('./lib/egg_router'); 5 | 6 | // for compact 7 | module.exports = KoaRouter; 8 | module.exports.KoaRouter = KoaRouter; 9 | module.exports.EggRouter = EggRouter; 10 | 11 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-router/lib/layer.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('egg-router'); 2 | var pathToRegExp = require('path-to-regexp'); 3 | var uri = require('urijs'); 4 | 5 | module.exports = Layer; 6 | 7 | /** 8 | * Initialize a new routing Layer with given `method`, `path`, and `middleware`. 9 | * 10 | * @param {String|RegExp} path Path string or regular expression. 11 | * @param {Array} methods Array of HTTP verbs. 12 | * @param {Array} middleware Layer callback/middleware or series of. 13 | * @param {Object=} opts 14 | * @param {String=} opts.name route name 15 | * @param {String=} opts.sensitive case sensitive (default: false) 16 | * @param {String=} opts.strict require the trailing slash (default: false) 17 | * @returns {Layer} 18 | * @private 19 | */ 20 | 21 | function Layer(path, methods, middleware, opts) { 22 | this.opts = opts || {}; 23 | this.name = this.opts.name || null; 24 | this.methods = []; 25 | this.paramNames = []; 26 | this.stack = Array.isArray(middleware) ? middleware : [middleware]; 27 | 28 | methods.forEach(function(method) { 29 | var l = this.methods.push(method.toUpperCase()); 30 | if (this.methods[l-1] === 'GET') { 31 | this.methods.unshift('HEAD'); 32 | } 33 | }, this); 34 | 35 | // ensure middleware is a function 36 | this.stack.forEach(function(fn) { 37 | var type = (typeof fn); 38 | if (type !== 'function') { 39 | throw new Error( 40 | methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` " 41 | + "must be a function, not `" + type + "`" 42 | ); 43 | } 44 | }, this); 45 | 46 | this.path = path; 47 | this.regexp = pathToRegExp(path, this.paramNames, this.opts); 48 | 49 | debug('defined route %s %s', this.methods, this.opts.prefix + this.path); 50 | }; 51 | 52 | /** 53 | * Returns whether request `path` matches route. 54 | * 55 | * @param {String} path 56 | * @returns {Boolean} 57 | * @private 58 | */ 59 | 60 | Layer.prototype.match = function (path) { 61 | return this.regexp.test(path); 62 | }; 63 | 64 | /** 65 | * Returns map of URL parameters for given `path` and `paramNames`. 66 | * 67 | * @param {String} path 68 | * @param {Array.} captures 69 | * @param {Object=} existingParams 70 | * @returns {Object} 71 | * @private 72 | */ 73 | 74 | Layer.prototype.params = function (path, captures, existingParams) { 75 | var params = existingParams || {}; 76 | 77 | for (var len = captures.length, i=0; i} 92 | * @private 93 | */ 94 | 95 | Layer.prototype.captures = function (path) { 96 | if (this.opts.ignoreCaptures) return []; 97 | return path.match(this.regexp).slice(1); 98 | }; 99 | 100 | /** 101 | * Generate URL for route using given `params`. 102 | * 103 | * @example 104 | * 105 | * ```javascript 106 | * var route = new Layer(['GET'], '/users/:id', fn); 107 | * 108 | * route.url({ id: 123 }); // => "/users/123" 109 | * ``` 110 | * 111 | * @param {Object} params url parameters 112 | * @returns {String} 113 | * @private 114 | */ 115 | 116 | Layer.prototype.url = function (params, options) { 117 | var args = params; 118 | var url = this.path.replace(/\(\.\*\)/g, ''); 119 | var toPath = pathToRegExp.compile(url); 120 | var replaced; 121 | 122 | if (typeof params != 'object') { 123 | args = Array.prototype.slice.call(arguments); 124 | if (typeof args[args.length - 1] == 'object') { 125 | options = args[args.length - 1]; 126 | args = args.slice(0, args.length - 1); 127 | } 128 | } 129 | 130 | var tokens = pathToRegExp.parse(url); 131 | var replace = {}; 132 | 133 | if (args instanceof Array) { 134 | for (var len = tokens.length, i=0, j=0; i token.name)) { 138 | replace = params; 139 | } else { 140 | options = params; 141 | } 142 | 143 | replaced = toPath(replace); 144 | 145 | if (options && options.query) { 146 | var replaced = new uri(replaced) 147 | replaced.search(options.query); 148 | return replaced.toString(); 149 | } 150 | 151 | return replaced; 152 | }; 153 | 154 | /** 155 | * Run validations on route named parameters. 156 | * 157 | * @example 158 | * 159 | * ```javascript 160 | * router 161 | * .param('user', function (id, ctx, next) { 162 | * ctx.user = users[id]; 163 | * if (!user) return ctx.status = 404; 164 | * next(); 165 | * }) 166 | * .get('/users/:user', function (ctx, next) { 167 | * ctx.body = ctx.user; 168 | * }); 169 | * ``` 170 | * 171 | * @param {String} param 172 | * @param {Function} middleware 173 | * @returns {Layer} 174 | * @private 175 | */ 176 | 177 | Layer.prototype.param = function (param, fn) { 178 | var stack = this.stack; 179 | var params = this.paramNames; 180 | var middleware = function (ctx, next) { 181 | return fn.call(this, ctx.params[param], ctx, next); 182 | }; 183 | middleware.param = param; 184 | 185 | var names = params.map(function (p) { 186 | return p.name; 187 | }); 188 | 189 | var x = names.indexOf(param); 190 | if (x > -1) { 191 | // iterate through the stack, to figure out where to place the handler fn 192 | stack.some(function (fn, i) { 193 | // param handlers are always first, so when we find an fn w/o a param property, stop here 194 | // if the param handler at this part of the stack comes after the one we are adding, stop here 195 | if (!fn.param || names.indexOf(fn.param) > x) { 196 | // inject this param handler right before the current item 197 | stack.splice(i, 0, middleware); 198 | return true; // then break the loop 199 | } 200 | }); 201 | } 202 | 203 | return this; 204 | }; 205 | 206 | /** 207 | * Prefix route path. 208 | * 209 | * @param {String} prefix 210 | * @returns {Layer} 211 | * @private 212 | */ 213 | 214 | Layer.prototype.setPrefix = function (prefix) { 215 | if (this.path) { 216 | this.path = prefix + this.path; 217 | this.paramNames = []; 218 | this.regexp = pathToRegExp(this.path, this.paramNames, this.opts); 219 | } 220 | 221 | return this; 222 | }; 223 | 224 | /** 225 | * Safe decodeURIComponent, won't throw any error. 226 | * If `decodeURIComponent` error happen, just return the original value. 227 | * 228 | * @param {String} text 229 | * @returns {String} URL decode original string. 230 | * @private 231 | */ 232 | 233 | function safeDecodeURIComponent(text) { 234 | try { 235 | return decodeURIComponent(text); 236 | } catch (e) { 237 | return text; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/egg-router/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const convert = require('koa-convert'); 4 | const is = require('is-type-of'); 5 | const co = require('co'); 6 | 7 | module.exports = { 8 | async callFn(fn, args, ctx) { 9 | args = args || []; 10 | if (!is.function(fn)) return; 11 | if (is.generatorFunction(fn)) fn = co.wrap(fn); 12 | return ctx ? fn.call(ctx, ...args) : fn(...args); 13 | }, 14 | 15 | middleware(fn) { 16 | return is.generatorFunction(fn) ? convert(fn) : fn; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mm/check.ts: -------------------------------------------------------------------------------- 1 | import * as mm from './'; 2 | 3 | mm({}, 'fn', () => {}); 4 | mm.http.request('xxx', {}, {}); 5 | mm.https.requestError('/xxx', 'xxx', 'xxx'); 6 | mm.restore(); 7 | mm.error({}, 'xxx', 'xxx', {}, 123); 8 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mm/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const http: _MockMate.T100; 2 | declare const https: _MockMate.T100; 3 | declare const _MockMate: _MockMate.T101; 4 | declare namespace _MockMate { 5 | export interface T100 { 6 | request: (url: any, data: any, headers: any, delay?: number) => any; 7 | requestError: (url: string | RegExp, reqError: string | Error, resError: string | Error, delay?: number) => void; 8 | } 9 | export interface T101 { 10 | (...args: any[]): any; 11 | isMocked: any; 12 | /** 13 | * Mock async function error. 14 | * @param {Object} mod, module object 15 | * @param {String} method, mock module object method name. 16 | * @param {String|Error} error, error string message or error instance. 17 | * @param {Object} props, error properties 18 | * @param {Number} timeout, mock async callback timeout, default is 0. 19 | * @return {mm} this - mm 20 | */ 21 | error: (mod: any, method: string, error: string | Error, props: any, timeout: number) => any; 22 | /** 23 | * mock return callback(null, data1, data2). 24 | * 25 | * @param {Object} mod, module object 26 | * @param {String} method, mock module object method name. 27 | * @param {Array} datas, return datas array. 28 | * @param {Number} timeout, mock async callback timeout, default is 10. 29 | * @return {mm} this - mm 30 | */ 31 | datas: (mod: any, method: string, datas: any[], timeout: number) => any; 32 | /** 33 | * mock return callback(null, data). 34 | * 35 | * @param {Object} mod, module object 36 | * @param {String} method, mock module object method name. 37 | * @param {Object} data, return data. 38 | * @param {Number} timeout, mock async callback timeout, default is 0. 39 | * @return {mm} this - mm 40 | */ 41 | data: (mod: any, method: string, data: any, timeout: number) => any; 42 | /** 43 | * mock return callback(null, null). 44 | * 45 | * @param {Object} mod, module object 46 | * @param {String} method, mock module object method name. 47 | * @param {Number} [timeout], mock async callback timeout, default is 0. 48 | * @return {mm} this - mm 49 | */ 50 | empty: (mod: any, method: string, timeout?: number) => any; 51 | /** 52 | * mock function sync throw error 53 | * 54 | * @param {Object} mod, module object 55 | * @param {String} method, mock module object method name. 56 | * @param {String|Error} error, error string message or error instance. 57 | * @param {Object} [props], error properties 58 | */ 59 | syncError: (mod: any, method: string, error: string | Error, props?: any) => void; 60 | /** 61 | * mock function sync return data 62 | * 63 | * @param {Object} mod, module object 64 | * @param {String} method, mock module object method name. 65 | * @param {Object} data, return data. 66 | */ 67 | syncData: (mod: any, method: string, data: any) => void; 68 | /** 69 | * mock function sync return nothing 70 | * 71 | * @param {Object} mod, module object 72 | * @param {String} method, mock module object method name. 73 | */ 74 | syncEmpty: (mod: any, method: string) => void; 75 | http: typeof http; 76 | https: typeof https; 77 | /** 78 | * mock child_process spawn 79 | * @param {Integer} code exit code 80 | * @param {String} stdout stdout 81 | * @param {String} stderr stderr 82 | * @param {Integer} timeout stdout/stderr/close event emit timeout 83 | */ 84 | spawn: (code: any, stdout: string, stderr: string, timeout: any) => void; 85 | /** 86 | * remove all mock effects. 87 | * @return {mm} this - mm 88 | */ 89 | restore: () => any; 90 | } 91 | } 92 | export = _MockMate; 93 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mm/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./mm'); 4 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mock-mate" 3 | } -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/check.ts: -------------------------------------------------------------------------------- 1 | import * as mus from './'; 2 | 3 | mus.utils.parser.parseAttr('xx', 'xxx').bind(undefined); 4 | mus.utils.processor.else({}); 5 | mus.customTags.set('dd', 123); 6 | const ast = mus.getAst('sss', 'sss'); 7 | const c = new mus.Compiler({ ast, scope: {}, ecomp: {} }); 8 | c.processAst({}, {}); 9 | ast.blockStart.trim(); 10 | const evt = new mus.utils.constant.EventEmitter(); 11 | evt.on('asd', () => {}); 12 | const m = new mus.Mus({}); 13 | m.configure({}); 14 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/compile/ast.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('../utils/utils'); 3 | const processor = require('./processor'); 4 | const constant = require('./constant'); 5 | const tagNameRE = /(end)?(\w+)/; 6 | const spaceReg = /(>|)(?:\t| )*(?:\r?\n)+(?:\t| )*(<|)/; 7 | let mus; 8 | 9 | class Ast { 10 | /** 11 | * ast class 12 | * @param {String} html 13 | * @param {Object} options 14 | * @param {String} options.blockStart 15 | * @param {String} options.blockEnd 16 | * @param {String} options.variableStart 17 | * @param {String} options.variableEnd 18 | * @param {Boolean} options.compress 19 | * @param {String} fileUrl 20 | */ 21 | constructor(html, options, fileUrl) { 22 | options = options || {}; 23 | this.root = []; 24 | this.parent = null; 25 | this.macro = new Map(); 26 | this.extends = null; 27 | this.scanIndex = 0; 28 | this.endIndex = 0; 29 | this.template = html; 30 | this.fileUrl = fileUrl; 31 | this.blockStart = options.blockStart || '{%'; 32 | this.blockEnd = options.blockEnd || '%}'; 33 | this.variableStart = options.variableStart || '{{'; 34 | this.variableEnd = options.variableEnd || '}}'; 35 | this.compress = options.compress; 36 | this.processor = processor; 37 | this.commentStart = '{#'; 38 | this.commentEnd = '#}'; 39 | 40 | // support extending processor 41 | if (options.processor) { 42 | this.processor = Object.assign({}, processor, options.processor); 43 | } 44 | 45 | if (this.blockStart === this.variableStart) { 46 | throw new Error('blockStart should be different with variableStart!'); 47 | } 48 | 49 | // create a regexp used to match leftStart 50 | this.startRegexp = utils.cache( 51 | `symbol_${this.blockStart}_${this.variableStart}_${this.commentStart}`, 52 | () => { 53 | // make sure can match the longest start string at first 54 | const str = [this.blockStart, this.variableStart, this.commentStart] 55 | .sort((a, b) => (a.length > b.length ? -1 : 1)) 56 | .map(item => utils.reStringFormat(item)) 57 | .join('|'); 58 | return new RegExp(str); 59 | } 60 | ); 61 | 62 | this.parse(html); 63 | this.optimize(); 64 | } 65 | 66 | parse(str) { 67 | if (!str) { 68 | return; 69 | } 70 | 71 | const collector = this.genCollector(); 72 | const parent = this.parent; 73 | const matches = str.match(this.startRegexp); 74 | const collectText = str => { 75 | const el = this.genNode(constant.TYPE_TEXT, str); 76 | this.processor.text(el); 77 | collector.push(el); 78 | }; 79 | 80 | if (!matches) { 81 | // parse end 82 | parent && utils.warn(`${parent.tag} was not closed`, parent); 83 | return collectText(str); 84 | } 85 | 86 | let element; 87 | this.endIndex = matches.index; 88 | const leftStart = matches[0]; 89 | const isTag = leftStart === this.blockStart; 90 | const isComment = leftStart === this.commentStart; 91 | collectText(str.substring(0, this.endIndex)); 92 | 93 | // get blockEnd or the other 94 | const rightEnd = isTag ? this.blockEnd : isComment ? this.commentEnd : this.variableEnd; 95 | str = this.advance(str, this.endIndex); 96 | 97 | // get rightEnd index 98 | this.endIndex = str.indexOf(rightEnd); 99 | const expression = str.substring(leftStart.length, this.endIndex); 100 | 101 | if (isComment) { 102 | // comment 103 | element = this.genNode(constant.TYPE_COM, leftStart + expression + rightEnd); 104 | element.comment = true; 105 | this.processor.comment(element); 106 | this.endIndex = this.endIndex >= 0 ? this.endIndex + rightEnd.length : str.length; 107 | } else if (this.endIndex < 0 || expression.indexOf(leftStart) >= 0) { 108 | // text 109 | collectText(leftStart); 110 | this.endIndex = leftStart.length; 111 | } else { 112 | this.endIndex = this.endIndex + rightEnd.length; 113 | 114 | if (parent && parent.raw) { 115 | // raw 116 | if (isTag && expression.trim() === 'endraw') { 117 | this.closeTag('raw'); 118 | } else { 119 | collectText(leftStart + expression + rightEnd); 120 | } 121 | } else if (isTag) { 122 | // tag 123 | const matches = expression.match(tagNameRE); 124 | const tagName = matches[0]; 125 | const isCustom = mus && mus.customTags.has(tagName); 126 | const tagHandle = this.processor[tagName] 127 | || (isCustom ? this.processor.custom : null); 128 | 129 | if (tagHandle) { 130 | // create ast node 131 | element = this.genNode( 132 | constant.TYPE_TAG, 133 | expression.substring(matches.index + tagName.length).trim() 134 | ); 135 | 136 | element[tagName] = true; 137 | element.tag = tagName; 138 | tagHandle.apply( 139 | this.processor, 140 | [element, isCustom ? mus.customTags.get(tagName) : null] 141 | ); 142 | 143 | if (!element.isUnary) { 144 | this.parent = element; 145 | } 146 | } else if (matches[1]) { 147 | this.closeTag(matches[2]); 148 | } else { 149 | utils.throw(`unknown tag '${expression.trim()}'`, this.genNode(null)); 150 | } 151 | } else { 152 | // variable 153 | element = this.genNode(constant.TYPE_VAR, expression); 154 | this.processor.variable(element); 155 | } 156 | } 157 | 158 | element && collector.push(element); 159 | this.parse(this.advance(str, this.endIndex)); 160 | } 161 | 162 | optimize(list = this.root) { 163 | const newList = []; 164 | 165 | for (let i = 0; i < list.length; i++) { 166 | const node = list[i]; 167 | const lastNode = newList[newList.length - 1]; 168 | 169 | if (node.type === constant.TYPE_TEXT) { 170 | if (this.compress) { 171 | let text = node.text; 172 | let matches; 173 | let newText = ''; 174 | 175 | // compress template 176 | while ((matches = text.match(spaceReg))) { 177 | const l = matches[1] || ''; 178 | const r = matches[2] || ''; 179 | const t = text.substring(0, matches.index); 180 | newText += t + l; 181 | 182 | // prevent comment in javascript or css 183 | if (t.indexOf('//') >= 0) { 184 | newText += '\n'; 185 | } 186 | 187 | newText += r; 188 | text = text.substring(matches.index + matches[0].length); 189 | } 190 | 191 | node.text = newText + text; 192 | } 193 | 194 | if (lastNode && lastNode.type === constant.TYPE_TEXT) { 195 | lastNode.text += node.text; 196 | } else { 197 | newList.push(node); 198 | } 199 | } else { 200 | if (!node.isAlone) { 201 | newList.push(node); 202 | } 203 | 204 | if (node.children) { 205 | node.children = this.optimize(node.children); 206 | } 207 | } 208 | } 209 | 210 | if (list === this.root) { 211 | this.root = newList; 212 | } 213 | 214 | return newList; 215 | } 216 | 217 | genNode(type, expr) { 218 | return { 219 | type, 220 | parent: this.parent, 221 | text: expr, 222 | _index: this.scanIndex, 223 | _len: this.endIndex, 224 | _ast: this, 225 | }; 226 | } 227 | 228 | advance(str, index) { 229 | this.scanIndex += index; 230 | return str.substring(index); 231 | } 232 | 233 | genCollector() { 234 | return this.parent 235 | ? (this.parent.children = this.parent.children || []) 236 | : this.root; 237 | } 238 | 239 | // close block 240 | // change current parent 241 | closeTag(tagName) { 242 | const p = this.parent; 243 | if (p) { 244 | this.parent = this.parent.parent; 245 | 246 | if (p.tag !== tagName) { 247 | if (!p.else && !p.elseif && !p.elif) { 248 | utils.warn(`${p.tag} was not closed`, p); 249 | } 250 | return this.closeTag(tagName); 251 | } else { 252 | return p; 253 | } 254 | } 255 | } 256 | } 257 | 258 | module.exports = function(html, options, fileUrl, musObj) { 259 | mus = musObj; 260 | const ast = new Ast(html, options, fileUrl); 261 | mus = null; 262 | return ast; 263 | }; 264 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/compile/compile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../utils/utils'); 4 | const constant = require('./constant'); 5 | let mus; 6 | 7 | class Compiler { 8 | constructor({ ast, scope = {}, ecomp }) { 9 | this.ast = ast; 10 | this.scope = scope; 11 | 12 | // add global function 13 | scope.range = scope.hasOwnProperty('range') ? scope.range : utils.range; 14 | scope.regular = scope.hasOwnProperty('regular') ? scope.regular : utils.regular; 15 | 16 | // compiler which extends this 17 | this.ecomp = ecomp; 18 | } 19 | 20 | compile(ast = this.ast, scope = this.scope) { 21 | if (ast.extends) { 22 | const fileUrl = computed(ast.extends, scope); 23 | const absoluteUrl = mus.resolveUrl(fileUrl); 24 | mus.relativeHook(absoluteUrl); 25 | 26 | return new Compiler({ 27 | ast: mus.getAstByUrl(absoluteUrl), 28 | scope: Object.assign({}, scope), 29 | ecomp: this, 30 | }).compile(); 31 | } else { 32 | return this.processAst(ast.root, scope); 33 | } 34 | } 35 | 36 | processAst(root, scope) { 37 | if (!root || !root.length) { 38 | return ''; 39 | } 40 | 41 | let html = ''; 42 | let i = 0; 43 | while (i < root.length) { 44 | const el = root[i]; 45 | if (el.type === constant.TYPE_TEXT) { 46 | // text handling 47 | html += el.text; 48 | } else if (el.type === constant.TYPE_VAR) { 49 | // variable handling 50 | html += this.processVariable(el, scope); 51 | } else if (el.type === constant.TYPE_TAG) { 52 | // block handling 53 | if (el.for) { 54 | html += this.processFor(el, scope); 55 | } else if (el.if) { 56 | html += this.processIf(el, scope); 57 | } else if (el.set) { 58 | scope[el.key] = computed(el, scope); 59 | } else if (el.block) { 60 | html += this.processBlock(el, scope); 61 | } else if (el.include) { 62 | html += this.processInclude(el, scope); 63 | } else if (el.import) { 64 | this.processImport(el, scope); 65 | } else if (el.filter) { 66 | scope._$r = this.processAst(el.children, scope); 67 | html += computed(el, scope); 68 | } else if (el.isCustom) { 69 | html += this.processCustom(el, scope); 70 | } else if (el.raw) { 71 | html += this.processAst(el.children); 72 | } 73 | } 74 | 75 | i++; 76 | } 77 | return html; 78 | } 79 | 80 | processVariable(el, scope) { 81 | if (el.method && this.ast.macro && this.ast.macro.has(el.method)) { 82 | const macroEl = this.ast.macro.get(el.method); 83 | utils.simpleSet(scope, el.method, macroEl.genRender( 84 | Object.assign({}, scope), // use new scope 85 | scope => (this.processAst(macroEl.children, scope)) 86 | )); 87 | } 88 | 89 | const result = computed(el, scope); 90 | return (el.safe || !mus.autoescape) ? result : utils.escape(result); 91 | } 92 | 93 | processImport(el, scope) { 94 | const fileUrl = computed(el, scope); 95 | const absoluteUrl = mus.resolveUrl(fileUrl); 96 | mus.relativeHook(absoluteUrl); 97 | const ast = mus.getAstByUrl(absoluteUrl); 98 | 99 | if (ast.macro.size) { 100 | // copy macro to current ast 101 | const prefix = el.item ? `${el.item}.` : ''; 102 | ast.macro.forEach((macroEl, key) => { 103 | this.ast.macro.set(`${prefix}${key}`, macroEl); 104 | }); 105 | } else { 106 | utils.warn('you are importing a non-macro template!', el); 107 | } 108 | } 109 | 110 | processInclude(el, scope) { 111 | const attr = el.attrFunc(scope); 112 | const fileUrl = attr._url; 113 | if (!fileUrl) { 114 | utils.throw('include url invalid!', el); 115 | } 116 | return include(fileUrl, Object.assign({}, this.scope, attr)); 117 | } 118 | 119 | processCustom(el, scope) { 120 | const attr = el.attrFunc ? el.attrFunc(scope) : {}; 121 | const result = el.render( 122 | attr, 123 | Object.assign({}, scope), 124 | { 125 | el, 126 | fileUrl: el._ast.fileUrl, 127 | include, 128 | compile: this.processAst.bind(this), 129 | } 130 | ); 131 | return (typeof result === 'string') ? result : ''; 132 | } 133 | 134 | processBlock(el, scope) { 135 | const extendBlock = this.ecomp 136 | && this.ecomp.ast.blocks 137 | && this.ecomp.ast.blocks.get(el.name); 138 | 139 | if (extendBlock) { 140 | return this.ecomp.processAst(extendBlock.children, scope); 141 | } else { 142 | return this.processAst(el.children, scope); 143 | } 144 | } 145 | 146 | processFor(el, scope) { 147 | let html = ''; 148 | let loopScope; 149 | const result = computed(el, scope); 150 | utils.forEach(result, (value, key, index, len) => { 151 | loopScope = loopScope || Object.assign({}, scope); 152 | loopScope[el.value] = value; 153 | loopScope.loop = { 154 | index: index + 1, 155 | index0: index, 156 | length: len, 157 | }; 158 | 159 | if (el.index) { 160 | loopScope[el.index] = key; 161 | } 162 | 163 | html += this.processAst(el.children, loopScope); 164 | }); 165 | return html; 166 | } 167 | 168 | processIf(el, scope) { 169 | // check if 170 | if (computed(el, scope)) { 171 | return this.processAst(el.children, scope); 172 | } else { 173 | // check else if 174 | if (el.elseifBlock) { 175 | let j = 0; 176 | while (j < el.elseifBlock.length) { 177 | const elseifBlock = el.elseifBlock[j]; 178 | if (computed(elseifBlock, scope)) { 179 | return this.processAst(elseifBlock.children, scope); 180 | } 181 | 182 | j++; 183 | } 184 | } 185 | 186 | // check else 187 | if (el.elseBlock) { 188 | return this.processAst(el.elseBlock.children, scope); 189 | } 190 | } 191 | 192 | return ''; 193 | } 194 | } 195 | 196 | function include(url, scope) { 197 | const absoluteUrl = mus.resolveUrl(url); 198 | mus.relativeHook(absoluteUrl); 199 | const includeAst = mus.getAstByUrl(absoluteUrl); 200 | return new Compiler({ 201 | ast: includeAst, 202 | scope, 203 | }).compile(); 204 | } 205 | 206 | function processFilter(filterName) { 207 | const filter = mus.filters[filterName]; 208 | 209 | if (!filter) { 210 | throw new Error(`unknown filter ${filterName}`); 211 | } 212 | 213 | return filter; 214 | } 215 | 216 | function computed(obj, scope, el) { 217 | let result; 218 | 219 | try { 220 | result = obj.render(scope, processFilter); 221 | } catch (e) { 222 | // only catch the not defined error 223 | const msg = e.message; 224 | if (msg.indexOf('is not defined') >= 0 || msg.indexOf('Cannot read property') >= 0) { 225 | result = ''; 226 | } else { 227 | utils.throw(e.message, el || obj); 228 | } 229 | } 230 | 231 | return result; 232 | } 233 | 234 | module.exports = function(ast, scope, musObj) { 235 | mus = musObj; 236 | const html = new Compiler({ ast, scope }).compile(); 237 | mus = null; 238 | return html; 239 | }; 240 | 241 | module.exports.Compiler = Compiler; 242 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/compile/constant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { EventEmitter } = require('events'); 4 | 5 | exports.EventEmitter = EventEmitter; 6 | exports.TYPE_TAG = 1; 7 | exports.TYPE_TEXT = 2; 8 | exports.TYPE_VAR = 3; 9 | exports.TYPE_COM = 4; 10 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/compile/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('../utils/utils'); 3 | 4 | // match obj.test || obj.test.test 5 | const objectRE = /^\w+(?:\.[\w\-'"]+)*$/; 6 | const otherRE = /^(true|false|null|NaN|undefined|\d+)$/; 7 | const scopeOpen = '{(['; 8 | const scopeClose = '})]'; 9 | const scopeOpt = scopeOpen + scopeClose; 10 | const operators = scopeOpt + '%+-<>!?*/:=&|,; \r\n'; 11 | const regFlags = 'igmy'; 12 | const optMapping = { 13 | not: '!', 14 | or: '||', 15 | and: '&&', 16 | }; 17 | 18 | exports.constant = require('./constant'); 19 | 20 | exports.parseSpaceAttr = function(expr, cb) { 21 | const result = exports.splitOperator(expr); 22 | const fragments = result.fragments; 23 | let key = ''; 24 | let str = ''; 25 | let _str = ''; 26 | let i = 0; 27 | const setValue = (s, _s) => { 28 | cb(key.trim(), s.trim(), (_s || _str).trim()); 29 | key = ''; 30 | }; 31 | 32 | while (i < fragments.length) { 33 | const frag = fragments[i]; 34 | 35 | // split key and value by equal sign 36 | if (frag.expr === '=') { 37 | str = str.trim(); 38 | const lastSepIndex = str.lastIndexOf(' '); 39 | if (lastSepIndex < 0) { 40 | key = _str; 41 | } else { 42 | _str = _str.trim(); 43 | const _index = _str.lastIndexOf(' '); 44 | setValue(str.substring(0, lastSepIndex), _str.substring(0, _index)); 45 | key = _str.substring(_index + 1); 46 | } 47 | _str = str = ''; 48 | } else { 49 | str += (frag.type === 'prop') 50 | ? `_$o.${frag.expr}` 51 | : frag.expr; 52 | _str += frag.expr; 53 | } 54 | 55 | i++; 56 | } 57 | 58 | setValue(str); 59 | }; 60 | 61 | exports.parseNormalAttr = function(d, expr, cb) { 62 | if (!expr) { 63 | return; 64 | } 65 | 66 | if (d === ' ') { 67 | return exports.parseSpaceAttr(expr, cb); 68 | } 69 | 70 | let i = 0; 71 | let key; 72 | let str = ''; 73 | let _str = ''; 74 | const result = exports.splitOperator(expr); 75 | const fragments = result.fragments; 76 | const scopeQueue = []; 77 | const setValue = () => { 78 | cb(key, str.trim(), _str.trim()); 79 | _str = str = key = ''; 80 | }; 81 | 82 | while (i < fragments.length) { 83 | const frag = fragments[i]; 84 | let closeIndex; 85 | 86 | // comma maybe in object or array 87 | if (scopeOpen.includes(frag.expr)) { 88 | scopeQueue.push(frag.expr); 89 | } else if (scopeQueue.length && (closeIndex = scopeClose.indexOf(frag.expr)) >= 0) { 90 | const last = scopeQueue[scopeQueue.length - 1]; 91 | if (last === scopeOpen.charAt(closeIndex)) { 92 | scopeQueue.pop(); 93 | } 94 | } 95 | 96 | if (!scopeQueue.length && frag.expr === '=') { 97 | key = _str.trim(); 98 | _str = str = ''; 99 | } else if (!scopeQueue.length && frag.expr === d) { 100 | setValue(); 101 | } else { 102 | str += frag.type === 'prop' 103 | ? `_$o.${frag.expr}` 104 | : frag.expr; 105 | _str += frag.expr; 106 | } 107 | 108 | i++; 109 | } 110 | 111 | setValue(); 112 | }; 113 | 114 | // parse attribute on tag 115 | exports.parseAttr = function(expr, attrName = 'default') { 116 | let functionStr = ''; 117 | exports.parseNormalAttr(' ', expr, (key, value) => { 118 | functionStr += `_$s['${key || attrName}'] = ${value};`; 119 | }); 120 | functionStr = `_$s = _$s || {}; ${functionStr} return _$s`; 121 | return new Function('_$o', '_$s', functionStr); 122 | }; 123 | 124 | // parse macro expression 125 | exports.parseMacroExpr = function(expr) { 126 | const startIndex = expr.indexOf('('); 127 | const endIndex = expr.lastIndexOf(')'); 128 | const hasArgs = startIndex > 0; 129 | const methodName = hasArgs ? expr.substring(0, startIndex) : expr; 130 | const argsString = hasArgs ? expr.substring(startIndex + 1, endIndex).trim() : null; 131 | let setScope = ''; 132 | let argIndex = 0; 133 | exports.parseNormalAttr(',', argsString, (key, value, base) => { 134 | if (!key) { 135 | key = base; 136 | value = null; 137 | } 138 | setScope += `_$o['${key}']=arguments.length>=${argIndex + 1}?`; 139 | setScope += `arguments[${argIndex}]:${value || '""'};`; 140 | argIndex++; 141 | }); 142 | 143 | const funcStr = `return function(){${setScope}return _$p(_$o);}`; 144 | const genRender = new Function('_$o', '_$p', funcStr); 145 | return { 146 | methodName: methodName.trim(), 147 | genRender, 148 | }; 149 | }; 150 | 151 | exports.parseCommonExpr = function(expr) { 152 | if (!(expr = expr.trim())) { 153 | throw new Error('parse error, expression invalid'); 154 | } 155 | 156 | const result = exports.splitOperator(expr); 157 | const fragments = result.fragments; 158 | let item; 159 | let safe = false; 160 | let filterStart = false; 161 | let filterName = ''; 162 | let filterArgs = ''; 163 | let funcStr = ''; 164 | 165 | function wrapFilter() { 166 | if (!filterName) { 167 | return; 168 | } 169 | 170 | funcStr = `_$f('${filterName}')(${funcStr}`; 171 | filterArgs = filterArgs.trim(); 172 | if (!filterArgs) { 173 | funcStr += ')'; 174 | } else { 175 | const l = filterArgs.indexOf('('); 176 | const r = filterArgs.lastIndexOf(')'); 177 | if (l < 0 || r < 0) { 178 | throw new Error(`filter invalid: ${filterArgs}`); 179 | } 180 | const argString = filterArgs.substring(l + 1, r).trim(); 181 | funcStr += argString ? `,${argString})` : ')'; 182 | } 183 | 184 | filterName = filterArgs = ''; 185 | } 186 | 187 | // compose the fragments 188 | while ((item = fragments.shift())) { 189 | if (item.expr === '|') { 190 | filterStart = true; 191 | wrapFilter(); 192 | continue; 193 | } 194 | 195 | const isProp = item.type === 'prop'; 196 | if (!filterStart || filterName) { 197 | const added = (isProp ? '_$o.' : '') + item.expr; 198 | if (filterName) { 199 | filterArgs += added; 200 | } else { 201 | funcStr += added; 202 | } 203 | } else if (isProp) { 204 | if (item.expr === 'safe') { 205 | safe = true; 206 | } else { 207 | filterName = item.expr; 208 | } 209 | } 210 | } 211 | 212 | if (filterStart) { 213 | wrapFilter(); 214 | } 215 | 216 | // create render function 217 | funcStr = `var result = ${utils.nlEscape(`${funcStr}`)};`; 218 | funcStr += 'return (result === undefined || result === null) ? "" : result;'; 219 | 220 | return { 221 | safe, 222 | render: new Function('_$o', '_$f', funcStr), 223 | }; 224 | }; 225 | 226 | // split expression by operator 227 | exports.splitOperator = function(expr) { 228 | let quot = null; 229 | let regexpStart = false; 230 | let regEsc = false; 231 | let i = 0; 232 | let savor = ''; 233 | let lastEl; 234 | let lastOpt; 235 | let last; 236 | const fragments = []; 237 | 238 | while (i < expr.length) { 239 | const char = expr.charAt(i); 240 | i++; 241 | 242 | if (char === '\'' || char === '"') { 243 | if (quot === char) { 244 | collect(savor + char, 'str'); 245 | quot = null; 246 | savor = ''; 247 | continue; 248 | } else if (!quot) { 249 | quot = char; 250 | } 251 | } else if (!quot) { 252 | if (regexpStart && (char !== '/' || regEsc)) { 253 | // handle \/ in regexp 254 | regEsc = char === '\\' && !regEsc; 255 | last.expr += char; 256 | continue; 257 | } else if (operators.includes(char)) { 258 | if (char === '/') { 259 | if (regexpStart) { 260 | regexpStart = false; 261 | last.expr += char; 262 | // collect regexp's flag 263 | let flag; 264 | while (regFlags.includes(flag = expr.charAt(i))) { 265 | last.expr += flag; 266 | i++; 267 | } 268 | regEsc = false; 269 | continue; 270 | } else if (savor === 'r') { 271 | regexpStart = true; 272 | collect(char, 'reg'); 273 | savor = ''; 274 | continue; 275 | } 276 | } 277 | 278 | if (savor) { 279 | if (optMapping.hasOwnProperty(savor)) { 280 | collectOpt(optMapping[savor]); 281 | } else { 282 | collect(savor); 283 | } 284 | } 285 | 286 | collectOpt(char); 287 | savor = ''; 288 | continue; 289 | } 290 | } 291 | 292 | savor += char; 293 | 294 | if (savor && i === expr.length) { 295 | collect(savor); 296 | } 297 | } 298 | 299 | // collect operator 300 | function collectOpt(char) { 301 | if (char === '\r') { 302 | return; 303 | } 304 | 305 | char = char === '\n' ? ' ' : char; 306 | // merge the same operator 307 | if (last && last.unit === char && !scopeOpt.includes(char)) { 308 | last.expr += char; 309 | } else { 310 | const optEl = { 311 | expr: char, 312 | unit: char, 313 | type: 'opt', 314 | }; 315 | 316 | // mark def type, like 'key' in '{ key: value }', 317 | // which is no need to prepend '_$o' 318 | let isDef = char === ':' && lastOpt && (lastOpt.expr === '{' || lastOpt.expr === ','); 319 | isDef = isDef && lastEl && lastEl.type === 'prop'; 320 | if (isDef) { 321 | lastEl.type = 'def'; 322 | } 323 | 324 | if (char !== ' ') { 325 | lastOpt = optEl; 326 | } 327 | 328 | fragments.push(last = optEl); 329 | } 330 | } 331 | 332 | // collect property | base type | string 333 | function collect(str, type) { 334 | if (!type) { 335 | if (otherRE.test(str)) { 336 | // base type, null|undefined etc. 337 | type = 'base'; 338 | } else if (objectRE.test(str)) { 339 | // simple property 340 | type = 'prop'; 341 | } 342 | } 343 | 344 | fragments.push(last = lastEl = { 345 | expr: str, 346 | type, 347 | }); 348 | } 349 | 350 | return { 351 | fragments, 352 | }; 353 | }; 354 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/compile/processor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../utils/utils'); 4 | const parser = require('./parser'); 5 | const forTagRE = /^(\w+)(?:\s*,\s*(\w+))?\s+in\s([\s\S]+)$/; 6 | const functionRE = /^\s*([\w.]+)\(/; 7 | const conditionRE = /([\s\S]+(?= if )) if ([\s\S]+(?= else )|[\s\S]+)(?: else ([\s\S]+))?/; 8 | 9 | module.exports = { 10 | variable(el) { 11 | processExpression(el.text, el); 12 | 13 | if (functionRE.test(el.text)) { 14 | el.method = RegExp.$1; 15 | } 16 | }, 17 | 18 | // a hook for custom comment processor 19 | comment(el) { 20 | el.text = ''; 21 | }, 22 | 23 | // a hook for custom text processor 24 | text() {}, 25 | 26 | for(el) { 27 | if (!forTagRE.test(el.text)) { 28 | utils.throw('parse error, for expression invalid', el); 29 | } 30 | 31 | el.value = RegExp.$1; 32 | el.index = RegExp.$2; 33 | processExpression(RegExp.$3, el); 34 | }, 35 | 36 | if(el) { 37 | if (!el.text) { 38 | utils.throw('parse error, if condition invalid', el); 39 | } 40 | 41 | processExpression(el.text, el); 42 | }, 43 | 44 | else(el) { 45 | const ifEl = getIfEl(el); 46 | ifEl.elseBlock = el; 47 | }, 48 | 49 | elseif(el) { 50 | if (!el.text) { 51 | utils.throw('parse error, elseif condition invalid', el); 52 | } 53 | 54 | if (el.parent && el.parent.else) { 55 | utils.throw('parse error, else behind elseif', el); 56 | } 57 | 58 | const ifEl = getIfEl(el); 59 | ifEl.elseifBlock = ifEl.elseifBlock || []; 60 | ifEl.elseifBlock.push(el); 61 | processExpression(el.text, el); 62 | }, 63 | 64 | elif(el) { 65 | this.elseif(el); 66 | }, 67 | 68 | set(el) { 69 | if (!el.text) { 70 | utils.throw('parse error, set expression invalid', el); 71 | } 72 | 73 | const index = el.text.indexOf('='); 74 | el.key = el.text.slice(0, index).trim(); 75 | el.isUnary = true; 76 | processExpression(el.text.slice(index + 1), el); 77 | }, 78 | 79 | raw() {}, 80 | 81 | filter(el) { 82 | if (!el.text) { 83 | return utils.throw('parse error, filter function not found', el); 84 | } 85 | 86 | processExpression(`_$o._$r | ${el.text}`, el); 87 | }, 88 | 89 | import(el) { 90 | if (!el.text) { 91 | return utils.throw('parse error, import url not found', el); 92 | } 93 | 94 | const expArr = el.text.split(' as '); 95 | processExpression(expArr[0], el); 96 | el.item = expArr[1] && expArr[1].trim(); 97 | el.isUnary = true; 98 | }, 99 | 100 | macro(el) { 101 | if (!el.text) { 102 | return utils.throw('parse error, macro name was needed', el); 103 | } 104 | 105 | el.isAlone = true; 106 | const result = parser.parseMacroExpr(el.text); 107 | el._ast.macro.set(result.methodName, el); 108 | el.genRender = result.genRender; 109 | }, 110 | 111 | extends(el) { 112 | if (!el.text) { 113 | utils.throw('parse error, extends url invalid', el); 114 | } 115 | 116 | el.isUnary = el.isAlone = true; 117 | processExpression(el.text, el); 118 | el._ast.extends = el; 119 | }, 120 | 121 | block(el) { 122 | if (!el.text) { 123 | utils.throw('parse error, block name invalid', el); 124 | } 125 | 126 | el.name = el.text; 127 | el._ast.blocks = el._ast.blocks || new Map(); 128 | el._ast.blocks.set(el.name, el); 129 | }, 130 | 131 | include(el) { 132 | if (!el.text) { 133 | utils.throw('parse error, include url invalid', el); 134 | } 135 | 136 | el.isUnary = true; 137 | try { 138 | el.attrFunc = parser.parseAttr(el.text, '_url'); 139 | } catch (e) { 140 | utils.throw(e.message, el); 141 | } 142 | }, 143 | 144 | custom(el, extra) { 145 | el.isCustom = true; 146 | el.render = extra.render; 147 | el.isUnary = extra.unary; 148 | if (el.text && !extra.noAttr) { 149 | try { 150 | el.attrFunc = parser.parseAttr(el.text, extra.attrName); 151 | } catch (e) { 152 | utils.throw(e.message, el); 153 | } 154 | } 155 | }, 156 | }; 157 | 158 | function getIfEl(el) { 159 | const ifEl = el.parent ? (el.parent.ifBlock || el.parent) : null; 160 | if (!ifEl) { 161 | utils.throw('parse error, if block not found', el); 162 | } 163 | el.ifBlock = el.parent = ifEl; 164 | el.isAlone = true; 165 | return ifEl; 166 | } 167 | 168 | function processExpression(expr, el) { 169 | if (expr.indexOf(' if ') >= 0) { 170 | expr = expr.replace( 171 | conditionRE, 172 | (all, res, cond, res2) => `${cond}?${res}:${res2 || '""'}` 173 | ); 174 | } 175 | 176 | try { 177 | Object.assign(el, parser.parseCommonExpr(expr)); 178 | } catch (e) { 179 | utils.throw(e.message, el); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter as EventEmitter_1 } from 'events'; 2 | declare class Compiler { 3 | ast: any; 4 | scope: _Mus.T101; 5 | ecomp: any; 6 | constructor(T100: _Mus.T102); 7 | compile(ast?: any, scope?: _Mus.T101): any; 8 | processAst(root: any, scope: any): string; 9 | processVariable(el: any, scope: any): any; 10 | processImport(el: any, scope: any): void; 11 | processInclude(el: any, scope: any): any; 12 | processCustom(el: any, scope: any): string; 13 | processBlock(el: any, scope: any): any; 14 | processFor(el: any, scope: any): string; 15 | processIf(el: any, scope: any): string; 16 | } 17 | declare namespace _Constant { 18 | // tests/fixtures/plugin/mus/compile/constant.js 19 | export const EventEmitter: typeof EventEmitter_1; 20 | export const TYPE_TAG: number; 21 | export const TYPE_TEXT: number; 22 | export const TYPE_VAR: number; 23 | export const TYPE_COM: number; 24 | } 25 | declare namespace _Parser { 26 | // tests/fixtures/plugin/mus/compile/parser.js 27 | export const constant: typeof _Constant; 28 | export function parseSpaceAttr(expr: any, cb: any): void; 29 | export function parseNormalAttr(d: any, expr: any, cb: any): void; 30 | export function parseAttr(expr: any, attrName?: string): (...args: any[]) => any; 31 | export interface T104 { 32 | methodName: any; 33 | genRender: (...args: any[]) => any; 34 | } 35 | export function parseMacroExpr(expr: any): T104; 36 | export interface T105 { 37 | safe: boolean; 38 | render: (...args: any[]) => any; 39 | } 40 | export function parseCommonExpr(expr: any): T105; 41 | export interface T106 { 42 | fragments: any[]; 43 | } 44 | export function splitOperator(expr: any): T106; 45 | } 46 | declare class Ast { 47 | root: any; 48 | parent: any; 49 | macro: Map; 50 | extends: any; 51 | scanIndex: number; 52 | endIndex: any; 53 | template: string; 54 | fileUrl: string; 55 | blockStart: string; 56 | blockEnd: string; 57 | variableStart: string; 58 | variableEnd: string; 59 | compress: boolean; 60 | processor: any; 61 | commentStart: string; 62 | commentEnd: string; 63 | startRegexp: any; 64 | /** 65 | * ast class 66 | * @param {String} html 67 | * @param {Object} options 68 | * @param {String} options.blockStart 69 | * @param {String} options.blockEnd 70 | * @param {String} options.variableStart 71 | * @param {String} options.variableEnd 72 | * @param {Boolean} options.compress 73 | * @param {String} fileUrl 74 | */ 75 | constructor(html: string, options: _Mus.T106, fileUrl: string); 76 | parse(str: any): void; 77 | optimize(list?: any): any[]; 78 | genNode(type: any, expr: any): any; 79 | advance(str: any, index: any): any; 80 | genCollector(): any; 81 | closeTag(tagName: any): any; 82 | } 83 | declare class Mus { 84 | customTags: Map; 85 | filters: any; 86 | constructor(options: any); 87 | Mus: typeof Mus; 88 | Compiler: typeof Compiler; 89 | utils: _Mus.T105; 90 | configure(options?: _Mus.T101): void; 91 | render(url: any, args: any): any; 92 | renderString(html: any, args: any): any; 93 | resolveUrl(filePath: any): any; 94 | getAstByUrl(filePath: any): Ast; 95 | readFile(filePath: any): string; 96 | /** 97 | * 98 | * @param {String} html 99 | * @param {String} fileUrl 100 | * @returns {ReturnType} 101 | */ 102 | getAst(html: string, fileUrl: string): Ast; 103 | setFilter(name: any, cb: any): void; 104 | setTag(name: any, tagOptions: any): void; 105 | } 106 | declare const _Mus: Mus; 107 | declare namespace _Mus { 108 | export interface T101 { 109 | [key: string]: any; 110 | } 111 | export interface T102 { 112 | ast: any; 113 | scope?: _Mus.T101; 114 | ecomp: any; 115 | } 116 | export interface T103 { 117 | forEach(obj: any, cb: any): void; 118 | nl2br(str: any): any; 119 | escape(str: any): string; 120 | nlEscape(str: any): any; 121 | simpleSet(obj: any, keyPath: any, value: any): void; 122 | throw(errMsg: any, el: any): any; 123 | warn(msg: any, el: any): void; 124 | stripScope(str: any): any; 125 | regular(expr: any, flag: any): RegExp; 126 | range(start: any, end: any, ...args: any[]): any[]; 127 | cache(key: any, value: any, c?: Map): any; 128 | reStringFormat(str: any): any; 129 | } 130 | export interface T104 { 131 | variable(el: any): void; 132 | comment(el: any): void; 133 | text(): void; 134 | for(el: any): void; 135 | if(el: any): void; 136 | else(el: any): void; 137 | elseif(el: any): void; 138 | elif(el: any): void; 139 | set(el: any): void; 140 | raw(): void; 141 | filter(el: any): any; 142 | import(el: any): any; 143 | macro(el: any): any; 144 | extends(el: any): void; 145 | block(el: any): void; 146 | include(el: any): void; 147 | custom(el: any, extra: any): void; 148 | } 149 | export interface T105 { 150 | utils: _Mus.T103; 151 | parser: typeof _Parser; 152 | processor: _Mus.T104; 153 | constant: typeof _Constant; 154 | } 155 | export interface T106 { 156 | blockStart: string; 157 | blockEnd: string; 158 | variableStart: string; 159 | variableEnd: string; 160 | compress: boolean; 161 | } 162 | } 163 | export = _Mus; 164 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const constant = require('./compile/constant'); 5 | const ast = require('./compile/ast'); 6 | const utils = require('./utils/utils'); 7 | const compile = require('./compile/compile'); 8 | const globalFilters = require('./utils/filters'); 9 | const parser = require('./compile/parser'); 10 | const processor = require('./compile/processor'); 11 | 12 | class Mus { 13 | constructor(options) { 14 | this.customTags = new Map(); 15 | this.filters = Object.assign({}, globalFilters); 16 | this.configure(options); 17 | } 18 | 19 | get Mus() { 20 | return Mus; 21 | } 22 | 23 | get Compiler() { 24 | return compile.Compiler; 25 | } 26 | 27 | get utils() { 28 | return { 29 | utils, 30 | parser, 31 | processor, 32 | constant, 33 | }; 34 | } 35 | 36 | configure(options = {}) { 37 | const noCache = options.hasOwnProperty('noCache') ? options.noCache : false; 38 | this.autoescape = options.hasOwnProperty('autoescape') 39 | ? options.autoescape 40 | : true; 41 | this.filters = Object.assign(this.filters, options.filters); 42 | this.cacheStore = noCache ? null : new Map(); 43 | this.baseDir = options.baseDir || __dirname; 44 | this.ext = options.ext || 'tpl'; 45 | this.options = options; 46 | this.relativeHook = options.relativeHook || function() {}; 47 | } 48 | 49 | render(url, args) { 50 | const filePath = this.resolveUrl(url); 51 | return compile(this.getAstByUrl(filePath), args, this); 52 | } 53 | 54 | renderString(html, args) { 55 | return compile(this.getAst(html), args, this); 56 | } 57 | 58 | resolveUrl(filePath) { 59 | const cb = () => { 60 | if (!path.extname(filePath)) { 61 | filePath += `.${this.ext}`; 62 | } 63 | 64 | return path.isAbsolute(filePath) 65 | ? filePath 66 | : path.resolve(this.baseDir, filePath); 67 | }; 68 | 69 | if (this.cacheStore) { 70 | return utils.cache(filePath, cb, this.cacheStore); 71 | } else { 72 | return cb(); 73 | } 74 | } 75 | 76 | getAstByUrl(filePath) { 77 | return this.getAst(null, filePath); 78 | } 79 | 80 | readFile(filePath) { 81 | if (!fs.existsSync(filePath)) { 82 | throw new Error(`${filePath} not found!`); 83 | } 84 | 85 | return fs.readFileSync(filePath).toString(); 86 | } 87 | 88 | /** 89 | * 90 | * @param {String} html 91 | * @param {String} fileUrl 92 | * @returns {ReturnType} 93 | */ 94 | getAst(html, fileUrl) { 95 | const cb = () => { 96 | html = html || this.readFile(fileUrl); 97 | return ast(html, this.options, fileUrl, this); 98 | }; 99 | 100 | if (this.cacheStore) { 101 | return utils.cache(`ast_${fileUrl || html}`, cb, this.cacheStore); 102 | } else { 103 | return cb(); 104 | } 105 | } 106 | 107 | setFilter(name, cb) { 108 | this.filters[name] = cb; 109 | } 110 | 111 | setTag(name, tagOptions) { 112 | if (!tagOptions || !tagOptions.render) { 113 | throw new Error('render function must exist!'); 114 | } 115 | 116 | if (processor.hasOwnProperty(name)) { 117 | throw new Error(`can't create build-in tag(${name})`); 118 | } 119 | 120 | this.customTags.set(name, tagOptions); 121 | } 122 | } 123 | 124 | module.exports = new Mus(); 125 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/utils/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('./utils'); 3 | 4 | module.exports = { 5 | nl2br(input) { 6 | return utils.nl2br(input); 7 | }, 8 | 9 | json(input) { 10 | if (!input) { 11 | return '{}'; 12 | } 13 | 14 | return JSON.stringify(input); 15 | }, 16 | 17 | escape(input) { 18 | return utils.escape(input); 19 | }, 20 | 21 | reverse(input) { 22 | return [].reverse.call(input); 23 | }, 24 | 25 | replace(input, key, newKey) { 26 | return String(input).replace( 27 | (key instanceof RegExp) ? key : new RegExp(key, 'g'), 28 | newKey || '' 29 | ); 30 | }, 31 | 32 | abs(input) { 33 | return Math.abs(input); 34 | }, 35 | 36 | join(input, key = '') { 37 | return [].join.call(input, key); 38 | }, 39 | 40 | lower(input) { 41 | return ''.toLowerCase.call(input); 42 | }, 43 | 44 | upper(input) { 45 | return ''.toUpperCase.call(input); 46 | }, 47 | 48 | slice(input, start, end) { 49 | return [].slice.call(input, start, end); 50 | }, 51 | 52 | trim(input) { 53 | return ''.trim.call(input); 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/mus/utils/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const underline = require('node-underline'); 3 | const entityMap = { 4 | '&': '&', 5 | '<': '<', 6 | '>': '>', 7 | '"': '"', 8 | "'": ''', 9 | '`': '`', 10 | '=': '=', 11 | }; 12 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; 13 | const nlRE = /\r?\n/g; 14 | const scopeRE = /_\$o\./g; 15 | const entityRE = new RegExp(`[${Object.keys(entityMap).join('')}]`, 'g'); 16 | const cache = new Map(); 17 | 18 | module.exports = { 19 | forEach(obj, cb) { 20 | const type = Object.prototype.toString.apply(obj); 21 | if (type === '[object Array]') { 22 | const len = obj.length; 23 | let i = 0; 24 | while (i < len) { 25 | cb(obj[i], i, i, len); 26 | i++; 27 | } 28 | } else if (type === '[object Object]') { 29 | const len = Object.keys(obj).length; 30 | let i = 0; 31 | for (const key in obj) { 32 | if (obj.hasOwnProperty(key)) { 33 | cb(obj[key], key, i, len); 34 | } 35 | i++; 36 | } 37 | } else { 38 | // do nothing 39 | } 40 | }, 41 | 42 | nl2br(str) { 43 | return (str && str.replace) 44 | ? str.replace(nlRE, '
') 45 | : str; 46 | }, 47 | 48 | escape(str) { 49 | return String(str).replace(entityRE, s => entityMap[s]); 50 | }, 51 | 52 | nlEscape(str) { 53 | return str.replace(nlRE, '\\n'); 54 | }, 55 | 56 | simpleSet(obj, keyPath, value) { 57 | const objList = keyPath.split('.'); 58 | if (objList.length > 1) { 59 | let key; 60 | while ((key = objList.shift())) { 61 | if (objList.length) { 62 | obj = obj[key] = obj[key] || {}; 63 | } else { 64 | obj[key] = value; 65 | } 66 | } 67 | } else { 68 | obj[keyPath] = value; 69 | } 70 | }, 71 | 72 | _genLocation(el) { 73 | const ast = el._ast; 74 | const index = el._index; 75 | const ul = underline(ast.template, { 76 | start: index, 77 | end: index + el._len, 78 | margin: 4, 79 | }); 80 | const fileUrl = `${ast.fileUrl ? ast.fileUrl : 'Template String'}:${ul.lineNumber}:${ul.columnNumber}`; 81 | return ['', fileUrl, '', ul.text].join('\n'); 82 | }, 83 | 84 | throw(errMsg, el) { 85 | const error = new Error(this.stripScope(errMsg)); 86 | if (el && el._ast) { 87 | error.stack = this._genLocation(el) + '\n\n' + error.stack; 88 | } 89 | throw error; 90 | }, 91 | 92 | warn(msg, el) { 93 | msg = 'WARNING: ' + msg; 94 | if (el && el._ast) { 95 | msg = this._genLocation(el) + '\n\n' + msg + '\n'; 96 | } 97 | process.stdout.write('\x1B[33m' + msg + '\x1B[0m\n'); 98 | }, 99 | 100 | stripScope(str) { 101 | return str.replace(scopeRE, ''); 102 | }, 103 | 104 | regular(expr, flag) { 105 | return new RegExp(expr, flag); 106 | }, 107 | 108 | range(start, end) { 109 | if (arguments.length === 1) { 110 | end = start; 111 | start = 0; 112 | } 113 | let len = end - start; 114 | const arr = new Array(len); 115 | while (len--) { 116 | arr[len] = start + len; 117 | } 118 | return arr; 119 | }, 120 | 121 | cache(key, value, c = cache) { 122 | if (c.has(key)) { 123 | return c.get(key); 124 | } 125 | 126 | const isFunction = typeof value === 'function'; 127 | const result = isFunction ? value() : value; 128 | if (result !== null && result !== undefined) { 129 | c.set(key, result); 130 | } 131 | return result; 132 | }, 133 | 134 | reStringFormat(str) { 135 | return str.replace(regexEscapeRE, '\\$&'); 136 | }, 137 | }; 138 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/ndir/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace _Ndir_1 { 2 | // tests/fixtures/plugin/ndir/lib/ndir.js 3 | /** 4 | * dir Walker Class. 5 | * 6 | * @constructor 7 | * @param {String} root Root path. 8 | * @param {Function(dirpath, files)} [onDir] The `dir` event callback. 9 | * @param {Function} [onEnd] The `end` event callback. 10 | * @param {Function} [onError] The `error` event callback. 11 | * @public 12 | */ 13 | export class Walk_1 { 14 | constructor(root: string, onDir: any, onEnd: (...args: any[]) => any, onError: (...args: any[]) => any); 15 | dirs: string[]; 16 | } 17 | export const Walk: typeof Walk_1; 18 | /** 19 | * Walking dir base on `Event`. 20 | * 21 | * @param {String} dir Start walking path. 22 | * @param {Function(dir)} [onDir] When a dir walk through, `emit('dir', dirpath, files)`. 23 | * @param {Function} [onEnd] When a dir walk over, `emit('end')`. 24 | * @param {Function} [onError] When stat a path error, `emit('error', err, path)`. 25 | * @return {Walk} dir walker instance. 26 | * @public 27 | */ 28 | export function walk(dir: string, onDir: any, onEnd?: (...args: any[]) => any, onError?: (...args: any[]) => any): Walk_1; 29 | /** 30 | * Copy file, auto create tofile dir if dir not exists. 31 | * 32 | * @param {String} fromfile, Source file path. 33 | * @param {String} tofile, Target file path. 34 | * @param {Function} callback 35 | * @public 36 | */ 37 | export function copyfile(fromfile: string, tofile: string, callback: (...args: any[]) => any): any; 38 | /** 39 | * mkdir if dir not exists, equal mkdir -p /path/foo/bar 40 | * 41 | * @param {String} dir 42 | * @param {Number} [mode] file mode, default is 0777. 43 | * @param {Function} [callback] 44 | * @public 45 | */ 46 | export function mkdir(dir: string, mode?: number, callback?: (...args: any[]) => any): void; 47 | export function mkdirp(dir: string, mode?: number, callback?: (...args: any[]) => any): void; 48 | /** 49 | * Read stream data line by line. 50 | * 51 | * @constructor 52 | * @param {String|ReadStream} file File path or data stream object. 53 | */ 54 | export class LineReader { 55 | constructor(file: any); 56 | readstream: any; 57 | remainBuffers: any[]; 58 | } 59 | /** 60 | * Line data reader 61 | * 62 | * @example 63 | * ``` 64 | * var ndir = require('ndir'); 65 | * ndir.createLineReader('/tmp/access.log') 66 | * .on('line', function (line) { 67 | * console.log(line.toString()); 68 | * }) 69 | * .on('end', function () { 70 | * console.log('end'); 71 | * }) 72 | * .on('error', function (err) { 73 | * console.error(err); 74 | * }); 75 | * ``` 76 | * 77 | * @param {String|ReadStream} file, file path or a `ReadStream` object. 78 | */ 79 | export function createLineReader(file: any): LineReader; 80 | } 81 | declare const _Ndir: typeof _Ndir_1; 82 | export = _Ndir; 83 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/ndir/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/ndir'); -------------------------------------------------------------------------------- /tests/fixtures/plugin/ndir/lib/ndir.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ndir - lib/ndir.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | var util = require('util'); 14 | var EventEmitter = require('events').EventEmitter; 15 | fs.exists = fs.exists || path.exists; 16 | 17 | /** 18 | * dir Walker Class. 19 | * 20 | * @constructor 21 | * @param {String} root Root path. 22 | * @param {Function(dirpath, files)} [onDir] The `dir` event callback. 23 | * @param {Function} [onEnd] The `end` event callback. 24 | * @param {Function} [onError] The `error` event callback. 25 | * @public 26 | */ 27 | function Walk(root, onDir, onEnd, onError) { 28 | if (!(this instanceof Walk)) { 29 | return new Walk(root, onDir, onEnd, onError); 30 | } 31 | this.dirs = [path.resolve(root)]; 32 | if (onDir) { 33 | this.on('dir', onDir); 34 | } 35 | if (onEnd) { 36 | this.on('end', onEnd); 37 | } 38 | onError && this.on('error', onError); 39 | var self = this; 40 | // let listen `files` Event first. 41 | process.nextTick(function () { 42 | self.next(); 43 | }); 44 | } 45 | 46 | util.inherits(Walk, EventEmitter); 47 | 48 | exports.Walk = Walk; 49 | 50 | /** 51 | * Walking dir base on `Event`. 52 | * 53 | * @param {String} dir Start walking path. 54 | * @param {Function(dir)} [onDir] When a dir walk through, `emit('dir', dirpath, files)`. 55 | * @param {Function} [onEnd] When a dir walk over, `emit('end')`. 56 | * @param {Function} [onError] When stat a path error, `emit('error', err, path)`. 57 | * @return {Walk} dir walker instance. 58 | * @public 59 | */ 60 | exports.walk = function walk(dir, onDir, onEnd, onError) { 61 | return new Walk(dir, onDir, onEnd, onError); 62 | }; 63 | 64 | /** 65 | * Next move, if move to the end, 66 | * will `emit('end')` event. 67 | * 68 | * @private 69 | */ 70 | Walk.prototype.next = function () { 71 | var dir = this.dirs.shift(); 72 | if (!dir) { 73 | return this.emit('end'); 74 | } 75 | this._dir(dir); 76 | }; 77 | 78 | /** 79 | * @private 80 | */ 81 | Walk.prototype._dir = function (dir) { 82 | var self = this; 83 | fs.readdir(dir, function (err, files) { 84 | if (err) { 85 | self.emit('error', err, dir); 86 | return self.next(); 87 | } 88 | var infos = []; 89 | if (files.length === 0) { 90 | self.emit('dir', dir, infos); 91 | return self.next(); 92 | } 93 | var counter = 0; 94 | files.forEach(function (file) { 95 | var p = path.join(dir, file); 96 | fs.lstat(p, function (err, stats) { 97 | counter++; 98 | if (err) { 99 | self.emit('error', err, p); 100 | } else { 101 | infos.push([p, stats]); 102 | if (stats.isDirectory()) { 103 | self.dirs.push(p); 104 | } 105 | } 106 | if (counter === files.length) { 107 | self.emit('dir', dir, infos); 108 | self.next(); 109 | } 110 | }); 111 | }); 112 | }); 113 | }; 114 | 115 | /** 116 | * Copy file, auto create tofile dir if dir not exists. 117 | * 118 | * @param {String} fromfile, Source file path. 119 | * @param {String} tofile, Target file path. 120 | * @param {Function} callback 121 | * @public 122 | */ 123 | exports.copyfile = function copyfile(fromfile, tofile, callback) { 124 | fromfile = path.resolve(fromfile); 125 | tofile = path.resolve(tofile); 126 | if (fromfile === tofile) { 127 | var msg = 'cp: "' + fromfile + '" and "' + tofile + '" are identical (not copied).'; 128 | return callback(new Error(msg)); 129 | } 130 | exports.mkdir(path.dirname(tofile), function (err) { 131 | if (err) { 132 | return callback(err); 133 | } 134 | var ws = fs.createWriteStream(tofile); 135 | var rs = fs.createReadStream(fromfile); 136 | var onerr = function (err) { 137 | callback && callback(err); 138 | callback = null; 139 | }; 140 | ws.once('error', onerr); // if file not open, these is only error event will be emit. 141 | rs.once('error', onerr); 142 | ws.on('close', function () { 143 | // after file open, error event could be fire close event before. 144 | callback && callback(); 145 | callback = null; 146 | }); 147 | rs.pipe(ws); 148 | }); 149 | }; 150 | 151 | /** 152 | * @private 153 | */ 154 | function _mkdir(dir, mode, callback) { 155 | fs.exists(dir, function (exists) { 156 | if (exists) { 157 | return callback(); 158 | } 159 | fs.mkdir(dir, mode, callback); 160 | }); 161 | } 162 | 163 | /** 164 | * mkdir if dir not exists, equal mkdir -p /path/foo/bar 165 | * 166 | * @param {String} dir 167 | * @param {Number} [mode] file mode, default is 0777. 168 | * @param {Function} [callback] 169 | * @public 170 | */ 171 | exports.mkdir = function mkdir(dir, mode, callback) { 172 | if (typeof mode === 'function') { 173 | callback = mode; 174 | mode = 0777 & (~process.umask()); 175 | } 176 | var parent = path.dirname(dir); 177 | fs.exists(parent, function (exists) { 178 | if (exists) { 179 | return _mkdir(dir, mode, callback); 180 | } 181 | exports.mkdir(parent, mode, function (err) { 182 | if (err) { 183 | return callback(err); 184 | } 185 | _mkdir(dir, mode, callback); 186 | }); 187 | }); 188 | }; 189 | exports.mkdirp = exports.mkdir; 190 | 191 | /** 192 | * Read stream data line by line. 193 | * 194 | * @constructor 195 | * @param {String|ReadStream} file File path or data stream object. 196 | */ 197 | function LineReader(file) { 198 | if (typeof file === 'string') { 199 | this.readstream = fs.createReadStream(file); 200 | } else { 201 | this.readstream = file; 202 | } 203 | this.remainBuffers = []; 204 | var self = this; 205 | this.readstream.on('data', function (data) { 206 | self.ondata(data); 207 | }); 208 | this.readstream.on('error', function (err) { 209 | self.emit('error', err); 210 | }); 211 | this.readstream.on('end', function () { 212 | self.emit('end'); 213 | }); 214 | } 215 | util.inherits(LineReader, EventEmitter); 216 | 217 | /** 218 | * `Stream` data event handler. 219 | * 220 | * @param {Buffer} data 221 | * @private 222 | */ 223 | LineReader.prototype.ondata = function (data) { 224 | var i = 0; 225 | var found = false; 226 | for (var l = data.length; i < l; i++) { 227 | if (data[i] === 10) { 228 | found = true; 229 | break; 230 | } 231 | } 232 | if (!found) { 233 | this.remainBuffers.push(data); 234 | return; 235 | } 236 | var line = null; 237 | if (this.remainBuffers.length > 0) { 238 | var size = i; 239 | var j, jl = this.remainBuffers.length; 240 | for (j = 0; j < jl; j++) { 241 | size += this.remainBuffers[j].length; 242 | } 243 | line = new Buffer(size); 244 | var pos = 0; 245 | for (j = 0; j < jl; j++) { 246 | var buf = this.remainBuffers[j]; 247 | buf.copy(line, pos); 248 | pos += buf.length; 249 | } 250 | // check if `\n` is the first char in `data` 251 | if (i > 0) { 252 | data.copy(line, pos, 0, i); 253 | } 254 | this.remainBuffers = []; 255 | } else { 256 | line = data.slice(0, i); 257 | } 258 | this.emit('line', line); 259 | this.ondata(data.slice(i + 1)); 260 | }; 261 | 262 | /** 263 | * Line data reader 264 | * 265 | * @example 266 | * ``` 267 | * var ndir = require('ndir'); 268 | * ndir.createLineReader('/tmp/access.log') 269 | * .on('line', function (line) { 270 | * console.log(line.toString()); 271 | * }) 272 | * .on('end', function () { 273 | * console.log('end'); 274 | * }) 275 | * .on('error', function (err) { 276 | * console.error(err); 277 | * }); 278 | * ``` 279 | * 280 | * @param {String|ReadStream} file, file path or a `ReadStream` object. 281 | */ 282 | exports.createLineReader = function (file) { 283 | return new LineReader(file); 284 | }; -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/check.ts: -------------------------------------------------------------------------------- 1 | import * as urllib from './'; 2 | import u from './'; 3 | import * as assert from 'assert'; 4 | 5 | assert(u.TIMEOUT); 6 | assert(u.USER_AGENT); 7 | 8 | u.curl('/xxx', {}, () => {}); 9 | u.request('/xxxx', {}, () => {}); 10 | 11 | urllib.curl('/xxx', {}, () => {}); 12 | urllib.create({}).curl('/xxx', {}, () => {}); 13 | 14 | const client = new urllib.HttpClient({}); 15 | client.curl('/xxx', {}, () => {}); 16 | client.request('/xxx', {}, () => {}); 17 | 18 | const client2 = new urllib.HttpClient2({}); 19 | client2.curl('/xxx', {}); 20 | client2.request('/xxx', {}); 21 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/detect_proxy_agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('urllib:detect_proxy_agent'); 4 | var getProxyFromURI = require('./get_proxy_from_uri'); 5 | 6 | var proxyAgents = {}; 7 | 8 | function detectProxyAgent(uri, args) { 9 | if (!args.enableProxy && !process.env.URLLIB_ENABLE_PROXY) { 10 | return null; 11 | } 12 | var proxy = args.proxy || process.env.URLLIB_PROXY; 13 | if (!proxy) { 14 | proxy = getProxyFromURI(uri); 15 | if (!proxy) { 16 | return null; 17 | } 18 | } 19 | 20 | var proxyAgent = proxyAgents[proxy]; 21 | if (!proxyAgent) { 22 | debug('create new proxy %s', proxy); 23 | // lazy require, only support node >= 4 24 | proxyAgent = proxyAgents[proxy] = new require('proxy-agent')(proxy); 25 | } 26 | debug('get proxy: %s', proxy); 27 | return proxyAgent; 28 | } 29 | 30 | module.exports = detectProxyAgent; 31 | module.exports.proxyAgents = proxyAgents; 32 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/get_proxy_from_uri.js: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/request/request/blob/90cf8c743bb9fd6a4cb683a56fb7844c6b316866/lib/getProxyFromURI.js 2 | 3 | 'use strict' 4 | 5 | function formatHostname(hostname) { 6 | // canonicalize the hostname, so that 'oogle.com' won't match 'google.com' 7 | return hostname.replace(/^\.*/, '.').toLowerCase() 8 | } 9 | 10 | function parseNoProxyZone(zone) { 11 | zone = zone.trim().toLowerCase() 12 | 13 | var zoneParts = zone.split(':', 2) 14 | , zoneHost = formatHostname(zoneParts[0]) 15 | , zonePort = zoneParts[1] 16 | , hasPort = zone.indexOf(':') > -1 17 | 18 | return {hostname: zoneHost, port: zonePort, hasPort: hasPort} 19 | } 20 | 21 | function uriInNoProxy(uri, noProxy) { 22 | var port = uri.port || (uri.protocol === 'https:' ? '443' : '80') 23 | , hostname = formatHostname(uri.hostname) 24 | , noProxyList = noProxy.split(',') 25 | 26 | // iterate through the noProxyList until it finds a match. 27 | return noProxyList.map(parseNoProxyZone).some(function(noProxyZone) { 28 | var isMatchedAt = hostname.indexOf(noProxyZone.hostname) 29 | , hostnameMatched = ( 30 | isMatchedAt > -1 && 31 | (isMatchedAt === hostname.length - noProxyZone.hostname.length) 32 | ) 33 | 34 | if (noProxyZone.hasPort) { 35 | return (port === noProxyZone.port) && hostnameMatched 36 | } 37 | 38 | return hostnameMatched 39 | }) 40 | } 41 | 42 | function getProxyFromURI(uri) { 43 | // Decide the proper request proxy to use based on the request URI object and the 44 | // environmental variables (NO_PROXY, HTTP_PROXY, etc.) 45 | // respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html) 46 | 47 | var noProxy = process.env.NO_PROXY || process.env.no_proxy || '' 48 | 49 | // if the noProxy is a wildcard then return null 50 | 51 | if (noProxy === '*') { 52 | return null 53 | } 54 | 55 | // if the noProxy is not empty and the uri is found return null 56 | 57 | if (noProxy !== '' && uriInNoProxy(uri, noProxy)) { 58 | return null 59 | } 60 | 61 | // Check for HTTP or HTTPS Proxy in environment Else default to null 62 | 63 | if (uri.protocol === 'http:') { 64 | return process.env.HTTP_PROXY || 65 | process.env.http_proxy || null 66 | } 67 | 68 | if (uri.protocol === 'https:') { 69 | return process.env.HTTPS_PROXY || 70 | process.env.https_proxy || 71 | process.env.HTTP_PROXY || 72 | process.env.http_proxy || null 73 | } 74 | 75 | // if none of that works, return null 76 | // (What uri protocol are you using then?) 77 | 78 | return null 79 | } 80 | 81 | module.exports = getProxyFromURI 82 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/httpclient.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var utility = require('utility'); 6 | var urllib = require('./urllib'); 7 | 8 | module.exports = HttpClient; 9 | 10 | function HttpClient(options) { 11 | EventEmitter.call(this); 12 | options = options || {}; 13 | 14 | if (options.agent !== undefined) { 15 | this.agent = options.agent; 16 | this.hasCustomAgent = true; 17 | } else { 18 | this.agent = urllib.agent; 19 | this.hasCustomAgent = false; 20 | } 21 | 22 | if (options.httpsAgent !== undefined) { 23 | this.httpsAgent = options.httpsAgent; 24 | this.hasCustomHttpsAgent = true; 25 | } else { 26 | this.httpsAgent = urllib.httpsAgent; 27 | this.hasCustomHttpsAgent = false; 28 | } 29 | this.defaultArgs = options.defaultArgs; 30 | } 31 | util.inherits(HttpClient, EventEmitter); 32 | 33 | HttpClient.prototype.request = HttpClient.prototype.curl = function (url, args, callback) { 34 | if (typeof args === 'function') { 35 | callback = args; 36 | args = null; 37 | } 38 | args = args || {}; 39 | if (this.defaultArgs) { 40 | args = utility.assign({}, [ this.defaultArgs, args ]); 41 | } 42 | args.emitter = this; 43 | args.agent = getAgent(args.agent, this.agent); 44 | args.httpsAgent = getAgent(args.httpsAgent, this.httpsAgent); 45 | return urllib.request(url, args, callback); 46 | }; 47 | 48 | HttpClient.prototype.requestThunk = function (url, args) { 49 | args = args || {}; 50 | if (this.defaultArgs) { 51 | args = utility.assign({}, [ this.defaultArgs, args ]); 52 | } 53 | args.emitter = this; 54 | args.agent = getAgent(args.agent, this.agent); 55 | args.httpsAgent = getAgent(args.httpsAgent, this.httpsAgent); 56 | return urllib.requestThunk(url, args); 57 | }; 58 | 59 | function getAgent(agent, defaultAgent) { 60 | return agent === undefined ? defaultAgent : agent; 61 | } 62 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/httpclient2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var debug = require('debug')('urllib'); 5 | var ms = require('humanize-ms'); 6 | var HttpClient = require('./httpclient'); 7 | 8 | var _Promise; 9 | 10 | module.exports = HttpClient2; 11 | 12 | function HttpClient2(options) { 13 | HttpClient.call(this, options); 14 | } 15 | 16 | util.inherits(HttpClient2, HttpClient); 17 | 18 | HttpClient2.prototype.request = HttpClient2.prototype.curl = function request(url, args) { 19 | var self = this; 20 | args = args || {}; 21 | args.retry = args.retry || 0; 22 | if (args.retryDelay) { 23 | args.retryDelay = ms(args.retryDelay); 24 | } 25 | args.isRetry = args.isRetry || function(res) { 26 | return res.status >= 500; 27 | }; 28 | return HttpClient.prototype.request.call(self, url, args) 29 | .then(function(res) { 30 | if (args.retry > 0 && typeof args.isRetry === 'function' && args.isRetry(res)) { 31 | args.retry--; 32 | debug('retry request %s, remain %s', url, args.retry); 33 | if (args.retryDelay) { 34 | debug('retry after %sms', args.retryDelay); 35 | return sleep(args.retryDelay).then(function() { return self.request(url, args); }); 36 | } 37 | return self.request(url, args); 38 | } 39 | return res; 40 | }) 41 | .catch(function(err) { 42 | if (args.retry > 0) { 43 | args.retry--; 44 | debug('retry request %s, remain %s, err %s', url, args.retry, err); 45 | if (args.retryDelay) { 46 | debug('retry after %sms', args.retryDelay); 47 | return sleep(args.retryDelay).then(function() { return self.request(url, args); }); 48 | } 49 | return self.request(url, args); 50 | } 51 | throw err; 52 | }); 53 | }; 54 | 55 | HttpClient2.prototype.requestThunk = function requestThunk(url, args) { 56 | var self = this; 57 | return function(callback) { 58 | self.request(url, args) 59 | .then(function(res) { 60 | var cb = callback; 61 | // make sure cb(null, res) throw error won't emit catch callback below 62 | callback = null; 63 | cb(null, res); 64 | }) 65 | .catch(function(err) { 66 | if (!callback) { 67 | // TODO: how to handle this error? 68 | return; 69 | } 70 | callback(err); 71 | }); 72 | }; 73 | }; 74 | 75 | function sleep(ms) { 76 | if (!_Promise) { 77 | _Promise = require('any-promise'); 78 | } 79 | 80 | return new _Promise(function(resolve) { 81 | setTimeout(resolve, ms); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Agent } from 'http'; 2 | import { Agent as Agent_1 } from 'https'; 3 | export const USER_AGENT: any; 4 | export const TIMEOUT: number; 5 | export const TIMEOUTS: any[]; 6 | export const agent: any; 7 | export const httpsAgent: any; 8 | export function curl(url: any, args?: any, callback?: (...args: any[]) => any, ...args_1: any[]): any; 9 | export function request(url: any, args?: any, callback?: (...args: any[]) => any, ...args_1: any[]): any; 10 | export function requestWithCallback(url: any, args: any, callback: any, ...args_1: any[]): any; 11 | export function requestThunk(url: any, args: any): (callback: any) => void; 12 | declare class HttpClient_1 { 13 | constructor(options: any); 14 | curl(url: any, args: any, callback: any): any; 15 | request(url: any, args: any, callback: any): any; 16 | requestThunk(url: any, args: any): (callback: any) => void; 17 | agent: any; 18 | hasCustomAgent: any; 19 | httpsAgent: any; 20 | hasCustomHttpsAgent: any; 21 | defaultArgs: any; 22 | } 23 | export const HttpClient: typeof HttpClient_1; 24 | declare class HttpClient2_1 { 25 | constructor(options: any); 26 | curl(url: any, args: any): any; 27 | request(url: any, args: any): any; 28 | requestThunk(url: any, args: any): (callback: any) => void; 29 | } 30 | export const HttpClient2: typeof HttpClient2_1; 31 | export function create(options: any): HttpClient_1; 32 | export const bbb: number; 33 | export const abc: number; 34 | export const aaaa: string; 35 | declare namespace _Urllib_1 { 36 | // tests/fixtures/plugin/urllib/urllib.js 37 | export const USER_AGENT: any; 38 | export const agent: Agent; 39 | export const httpsAgent: Agent_1; 40 | /** 41 | * The default request timeout(in milliseconds). 42 | * @type {Number} 43 | * @const 44 | */ 45 | export const TIMEOUT: any; 46 | export const TIMEOUTS: any[]; 47 | /** 48 | * Handle all http request, both http and https support well. 49 | * 50 | * @example 51 | * 52 | * // GET http://httptest.cnodejs.net 53 | * urllib.request('http://httptest.cnodejs.net/test/get', function(err, data, res) {}); 54 | * // POST http://httptest.cnodejs.net 55 | * var args = { type: 'post', data: { foo: 'bar' } }; 56 | * urllib.request('http://httptest.cnodejs.net/test/post', args, function(err, data, res) {}); 57 | * 58 | * @param {String|Object} url 59 | * @param {Object} [args], optional 60 | * - {Object} [data]: request data, will auto be query stringify. 61 | * - {Boolean} [dataAsQueryString]: force convert `data` to query string. 62 | * - {String|Buffer} [content]: optional, if set content, `data` will ignore. 63 | * - {ReadStream} [stream]: read stream to sent. 64 | * - {WriteStream} [writeStream]: writable stream to save response data. 65 | * If you use this, callback's data should be null. 66 | * We will just `pipe(ws, {end: true})`. 67 | * - {consumeWriteStream} [true]: consume the writeStream, invoke the callback after writeStream close. 68 | * - {String} [method]: optional, could be GET | POST | DELETE | PUT, default is GET 69 | * - {String} [contentType]: optional, request data type, could be `json`, default is undefined 70 | * - {String} [dataType]: optional, response data type, could be `text` or `json`, default is buffer 71 | * - {Boolean|Function} [fixJSONCtlChars]: optional, fix the control characters (U+0000 through U+001F) 72 | * before JSON parse response. Default is `false`. 73 | * `fixJSONCtlChars` can be a function, will pass data to the first argument. e.g.: `data = fixJSONCtlChars(data)` 74 | * - {Object} [headers]: optional, request headers 75 | * - {Number|Array} [timeout]: request timeout(in milliseconds), default is `exports.TIMEOUTS containing connect timeout and response timeout` 76 | * - {Agent} [agent]: optional, http agent. Set `false` if you does not use agent. 77 | * - {Agent} [httpsAgent]: optional, https agent. Set `false` if you does not use agent. 78 | * - {String} [auth]: Basic authentication i.e. 'user:password' to compute an Authorization header. 79 | * - {String} [digestAuth]: Digest authentication i.e. 'user:password' to compute an Authorization header. 80 | * - {String|Buffer|Array} [ca]: An array of strings or Buffers of trusted certificates. 81 | * If this is omitted several well known "root" CAs will be used, like VeriSign. 82 | * These are used to authorize connections. 83 | * Notes: This is necessary only if the server uses the self-signed certificate 84 | * - {Boolean} [rejectUnauthorized]: If true, the server certificate is verified against the list of supplied CAs. 85 | * An 'error' event is emitted if verification fails. Default: true. 86 | * - {String|Buffer} [pfx]: A string or Buffer containing the private key, 87 | * certificate and CA certs of the server in PFX or PKCS12 format. 88 | * - {String|Buffer} [key]: A string or Buffer containing the private key of the client in PEM format. 89 | * Notes: This is necessary only if using the client certificate authentication 90 | * - {String|Buffer} [cert]: A string or Buffer containing the certificate key of the client in PEM format. 91 | * Notes: This is necessary only if using the client certificate authentication 92 | * - {String} [passphrase]: A string of passphrase for the private key or pfx. 93 | * - {String} [ciphers]: A string describing the ciphers to use or exclude. 94 | * - {String} [secureProtocol]: The SSL method to use, e.g. SSLv3_method to force SSL version 3. 95 | * The possible values depend on your installation of OpenSSL and are defined in the constant SSL_METHODS. 96 | * - {Boolean} [followRedirect]: Follow HTTP 3xx responses as redirects. defaults to false. 97 | * - {Number} [maxRedirects]: The maximum number of redirects to follow, defaults to 10. 98 | * - {Function(from, to)} [formatRedirectUrl]: Format the redirect url by your self. Default is `url.resolve(from, to)` 99 | * - {Function(options)} [beforeRequest]: Before request hook, you can change every thing here. 100 | * - {Boolean} [streaming]: let you get the res object when request connected, default is `false`. alias `customResponse` 101 | * - {Boolean} [gzip]: Accept gzip response content and auto decode it, default is `false`. 102 | * - {Boolean} [timing]: Enable timing or not, default is `false`. 103 | * - {Function} [lookup]: Custom DNS lookup function, default is `dns.lookup`. 104 | * Require node >= 4.0.0 and only work on `http` protocol. 105 | * - {Boolean} [enableProxy]: optional, enable proxy request. Default is `false`. 106 | * - {String|Object} [proxy]: optional proxy agent uri or options. Default is `null`. 107 | * @param {Function} [callback]: callback(error, data, res). If missing callback, will return a promise object. 108 | * @return {HttpRequest} req object. 109 | * @api public 110 | */ 111 | export function request(url: any, args?: any, callback?: (...args: any[]) => any, ...args_1: any[]): any; 112 | export function curl(url: any, args?: any, callback?: (...args: any[]) => any, ...args_1: any[]): any; 113 | export function requestThunk(url: any, args: any): (callback: any) => void; 114 | export function requestWithCallback(url: any, args: any, callback: any, ...args_1: any[]): any; 115 | } 116 | declare const urllib: typeof _Urllib_1; 117 | export default urllib; 118 | -------------------------------------------------------------------------------- /tests/fixtures/plugin/urllib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var urllib = require('./urllib'); 4 | 5 | exports.USER_AGENT = urllib.USER_AGENT; 6 | exports.TIMEOUT = urllib.TIMEOUT; 7 | exports.TIMEOUTS = urllib.TIMEOUTS; 8 | exports.agent = urllib.agent; 9 | exports.httpsAgent = urllib.httpsAgent; 10 | 11 | exports.curl = urllib.curl; 12 | exports.request = urllib.request; 13 | exports.requestWithCallback = urllib.requestWithCallback; 14 | exports.requestThunk = urllib.requestThunk; 15 | 16 | exports.HttpClient = require('./httpclient'); 17 | exports.HttpClient2 = require('./httpclient2'); 18 | 19 | exports.create = function (options) { 20 | return new exports.HttpClient(options); 21 | }; 22 | 23 | exports.abc = exports.bbb = 123; 24 | const bb = exports; 25 | bb.aaaa = '3333'; 26 | 27 | exports.default = urllib; 28 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import coffee from 'coffee'; 4 | import { create } from '../dist'; 5 | const tsconfigPlain = JSON.stringify({ 6 | compilerOptions: { 7 | strict: true, 8 | target: 'es2017', 9 | module: 'commonjs', 10 | moduleResolution: 'node', 11 | noImplicitAny: false, 12 | }, 13 | }, null, 2); 14 | 15 | function coffeeWork(task: coffee.Coffee) { 16 | return task 17 | .debug() 18 | .expect('code', 0) 19 | .end(); 20 | } 21 | 22 | async function checkDts(file: string, execute: boolean = true) { 23 | const dir = path.resolve(__dirname, './fixtures', file); 24 | const code = create(path.resolve(dir, 'index.js'))!.write(); 25 | const tscFile = require.resolve('typescript/bin/tsc'); 26 | fs.writeFileSync(path.resolve(dir, 'tsconfig.json'), tsconfigPlain); 27 | await coffeeWork(coffee.fork(tscFile, [ '-p', path.resolve(dir, 'tsconfig.json') ], { execArgv: [] })); 28 | const checkerTs = path.resolve(dir, 'check.js'); 29 | if (fs.existsSync(checkerTs) && execute) { 30 | await coffeeWork(coffee.fork(path.resolve(dir, 'check.js'), [], { execArgv: [] })); 31 | } 32 | return code; 33 | } 34 | 35 | describe('index.test.ts', () => { 36 | describe('normal', () => { 37 | it('normal#object', async () => { 38 | await checkDts('normal/object'); 39 | }); 40 | 41 | it('normal#function', async () => { 42 | await checkDts('normal/function'); 43 | }); 44 | 45 | it('normal#function.1', async () => { 46 | await checkDts('normal/function.1'); 47 | }); 48 | 49 | it('normal#class', async () => { 50 | await checkDts('normal/class'); 51 | }); 52 | 53 | it('normal#exports', async () => { 54 | await checkDts('normal/exports'); 55 | }); 56 | 57 | it('normal#exports.1', async () => { 58 | await checkDts('normal/exports.1', false); 59 | }); 60 | 61 | it('normal#custom', async () => { 62 | await checkDts('normal/custom'); 63 | }); 64 | 65 | it('normal#custom.1', async () => { 66 | await checkDts('normal/custom.1'); 67 | }); 68 | 69 | it('normal#prototype', async () => { 70 | await checkDts('normal/prototype'); 71 | }); 72 | 73 | it('normal#prototype.1', async () => { 74 | await checkDts('normal/prototype.1'); 75 | }); 76 | 77 | it('normal#jsdoc', async () => { 78 | await checkDts('normal/jsdoc'); 79 | }); 80 | 81 | it('normal#jsdoc.1', async () => { 82 | await checkDts('normal/jsdoc.1'); 83 | }); 84 | 85 | it('normal#esm', async () => { 86 | await checkDts('normal/esm'); 87 | }); 88 | 89 | it('normal#esm.1', async () => { 90 | await checkDts('normal/esm.1'); 91 | }); 92 | 93 | it('normal#esm.2', async () => { 94 | await checkDts('normal/esm.2'); 95 | }); 96 | }); 97 | 98 | describe('plugin', () => { 99 | it('plugin#egg-router', async () => { 100 | await checkDts('plugin/egg-router', false); 101 | }); 102 | 103 | it('plugin#mus', async () => { 104 | await checkDts('plugin/mus', false); 105 | }); 106 | 107 | it('plugin#egg-core', async () => { 108 | await checkDts('plugin/egg-core', false); 109 | }); 110 | 111 | it('plugin#urllib', async () => { 112 | await checkDts('plugin/urllib', false); 113 | }); 114 | 115 | it('plugin#mm', async () => { 116 | await checkDts('plugin/mm', false); 117 | }); 118 | 119 | it('plugin#cookies', async () => { 120 | await checkDts('plugin/cookies', false); 121 | }); 122 | 123 | it('plugin#ndir', async () => { 124 | await checkDts('plugin/ndir', false); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /tests/util.test.ts: -------------------------------------------------------------------------------- 1 | import { formatName } from '../dist/util'; 2 | 3 | describe('util.test.ts', () => { 4 | describe('formatName', () => { 5 | it('should format normal package names properly', () => { 6 | expect(formatName('JS2DTS')).toEqual('JS2DTS'); 7 | expect(formatName('js2dts')).toEqual('js2dts'); 8 | expect(formatName('did-util')).toEqual('didUtil'); 9 | }); 10 | 11 | it('should format scoped package names properly', () => { 12 | expect(formatName('@xmark/core')).toEqual('XmarkCore'); 13 | expect(formatName('@xmark/transform-landing-page')).toEqual('XmarkTransformLandingPage'); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "dist", 8 | "noImplicitAny": false, 9 | "allowSyntheticDefaultImports": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "esModuleInterop": true, 13 | "charset": "utf8", 14 | "allowJs": false, 15 | "pretty": true, 16 | "declaration": true, 17 | "declarationMap": true, 18 | "noEmitOnError": false, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "allowUnreachableCode": false, 22 | "allowUnusedLabels": false, 23 | "strictPropertyInitialization": false, 24 | "noFallthroughCasesInSwitch": true, 25 | "skipLibCheck": true, 26 | "skipDefaultLibCheck": true, 27 | "inlineSourceMap": true, 28 | "importHelpers": true 29 | }, 30 | "include": [ 31 | "./src/**/*", 32 | "typings/**/*" 33 | ] 34 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-egg", 3 | "rules": { 4 | "no-bitwise": false, 5 | "prefer-for-of": false, 6 | "no-angle-bracket-type-assertion": false, 7 | "no-duplicate-imports": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' { 2 | export const name: string; 3 | export const version: string; 4 | export const homepage: string; 5 | } --------------------------------------------------------------------------------