Skip to content

Commit

Permalink
fix: fix config types error (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
whxaxes committed Aug 21, 2019
1 parent a357f28 commit 368e2a9
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 52 deletions.
44 changes: 38 additions & 6 deletions src/generators/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as utils from '../utils';
const EXPORT_DEFAULT_FUNCTION = 1;
const EXPORT_DEFAULT = 2;
const EXPORT = 3;
const cache: { [key: string]: ImportItem } = {};
const globalCache: { [key: string]: { [key: string]: ImportItem } } = {};

export interface ImportItem {
import: string;
Expand All @@ -25,6 +25,7 @@ export const defaultConfig = {
export default function(config: TsGenConfig, baseConfig: TsHelperConfig) {
const fileList = config.fileList;
const dist = path.resolve(config.dtsDir, 'index.d.ts');
const cache = globalCache[baseConfig.id] = globalCache[baseConfig.id] || {};
if (!fileList.length) {
return { dist };
}
Expand All @@ -37,7 +38,13 @@ export default function(config: TsGenConfig, baseConfig: TsHelperConfig) {

// read from cache
if (!cache[abUrl] || config.file === abUrl) {
const type = checkConfigReturnType(abUrl);
const skipLibCheck = !!baseConfig.tsConfig.skipLibCheck;
const { type, usePowerPartial } = checkConfigReturnType(abUrl);

// skip when not usePowerPartial and skipLibCheck in ts file
// because it maybe cause types error.
if (path.extname(f) !== '.js' && !usePowerPartial && !skipLibCheck) return;

const { moduleName: sModuleName } = utils.getModuleObjByPath(f);
const moduleName = `Export${sModuleName}`;
const importContext = utils.getImportStr(
Expand Down Expand Up @@ -91,11 +98,36 @@ export default function(config: TsGenConfig, baseConfig: TsHelperConfig) {
// check config return type.
export function checkConfigReturnType(f: string) {
const result = utils.findExportNode(fs.readFileSync(f, 'utf-8'));
const resp: { type: number | undefined; usePowerPartial: boolean } = {
type: undefined,
usePowerPartial: false,
};

if (result.exportDefaultNode) {
return ts.isFunctionLike(result.exportDefaultNode)
? EXPORT_DEFAULT_FUNCTION
: EXPORT_DEFAULT;
const exportDefaultNode = result.exportDefaultNode;
if (ts.isFunctionLike(exportDefaultNode)) {
if ((ts.isFunctionDeclaration(exportDefaultNode) || ts.isArrowFunction(exportDefaultNode)) && exportDefaultNode.body) {
exportDefaultNode.body.forEachChild(tNode => {
if (!resp.usePowerPartial && ts.isVariableStatement(tNode)) {
// check wether use PowerPartial<EggAppInfo>
resp.usePowerPartial = !!tNode.declarationList.declarations.find(decl => {
let typeText = decl.type ? decl.type.getText() : undefined;
if (decl.initializer && ts.isAsExpression(decl.initializer) && decl.initializer.type) {
typeText = decl.initializer.type.getText();
}
return !!(typeText && typeText.includes('PowerPartial') && typeText.includes('EggAppConfig'));
});
}
});
}

resp.type = EXPORT_DEFAULT_FUNCTION;
} else {
resp.type = EXPORT_DEFAULT;
}
} else if (result.exportNodeList.length) {
return EXPORT;
resp.type = EXPORT;
}

return resp;
}
19 changes: 14 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { get as deepGet, set as deepSet } from 'dot-prop';
import { declMapping, dtsComment, dtsCommentRE } from './config';
import Watcher, { WatchItem } from './watcher';
import * as utils from './utils';
import { CompilerOptions } from 'typescript';
import glob from 'globby';
const isInUnitTest = process.env.NODE_ENV === 'test';

Expand All @@ -33,12 +34,16 @@ export interface TsHelperOption {
}

export type WatchItem = WatchItem;
export type TsHelperConfig = typeof defaultConfig;
export type TsHelperConfig = typeof defaultConfig & {
id: string;
tsConfig: CompilerOptions;
};

export type TsGenConfig = {
name: string;
dir: string;
dtsDir: string;
fileList: string[],
fileList: string[];
file?: string;
} & WatchItem;

Expand Down Expand Up @@ -314,26 +319,30 @@ export default class TsHelper extends EventEmitter {

// read from local file( default to tshelper | tsHelper )
(Array.isArray(configFile) ? configFile : [ configFile ]).forEach(f => {
this.mergeConfig(config, utils.requireFile(utils.getAbsoluteUrlByCwd(f, config.cwd)));
this.mergeConfig(config, utils.requireFile(path.resolve(config.cwd, f)));
});

// merge local config and options to config
this.mergeConfig(config, options);

// create extra config
config.id = `${Date.now()}-${Math.ceil(Math.random() * 1000000)}`;
config.tsConfig = utils.loadTsConfig(path.resolve(config.cwd, './tsconfig.json'));
}

// configure
// options > configFile > package.json
private configure(options: TsHelperOption) {
if (options.cwd) {
options.cwd = utils.getAbsoluteUrlByCwd(options.cwd, defaultConfig.cwd);
options.cwd = path.resolve(defaultConfig.cwd, options.cwd);
}

// base config
const config = { ...defaultConfig };
config.cwd = options.cwd || config.cwd;
config.framework = options.framework || defaultConfig.framework;
config.watchDirs = getDefaultWatchDirs(config);
config.typings = utils.getAbsoluteUrlByCwd(config.typings, config.cwd);
config.typings = path.resolve(config.cwd, config.typings);
this.config = config as TsHelperConfig;

// load watcher config
Expand Down
72 changes: 44 additions & 28 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export const JS_CONFIG = {
include: [ '**/*' ],
};

export const TS_CONFIG = {
export const TS_CONFIG: Partial<TsConfigJson> = {
compilerOptions: {
target: 'es2017',
module: 'commonjs',
target: ts.ScriptTarget.ES2017,
module: ts.ModuleKind.CommonJS,
strict: true,
noImplicitAny: false,
experimentalDecorators: true,
Expand All @@ -37,6 +37,11 @@ export const TS_CONFIG = {
},
};

export interface TsConfigJson {
extends: string;
compilerOptions: ts.CompilerOptions;
}

export interface GetEggInfoOpt {
async?: boolean;
env?: PlainObject<string>;
Expand Down Expand Up @@ -98,32 +103,20 @@ export function getEggInfo<T extends 'async' | 'sync' = 'sync'>(cwd: string, opt
exec(cmd, opt, err => {
caches.runningPromise = null;
if (err) reject(err);
resolve(end(getJson(fs.readFileSync(eggInfoPath, 'utf-8'))));
resolve(end(parseJson(fs.readFileSync(eggInfoPath, 'utf-8'))));
});
});
return caches.runningPromise;
} else {
try {
execSync(cmd, opt);
return end(getJson(fs.readFileSync(eggInfoPath, 'utf-8')));
return end(parseJson(fs.readFileSync(eggInfoPath, 'utf-8')));
} catch (e) {
return end({});
}
}
}

export function getJson(jsonStr: string) {
if (jsonStr) {
try {
return JSON.parse(jsonStr);
} catch (e) {
return {};
}
} else {
return {};
}
}

// convert string to same type with default value
export function convertString<T>(val: string | undefined, defaultVal: T): T {
if (val === undefined) return defaultVal;
Expand Down Expand Up @@ -235,10 +228,6 @@ export function log(msg: string, prefix: boolean = true) {
console.info(`${prefix ? '[egg-ts-helper] ' : ''}${msg}`);
}

export function getAbsoluteUrlByCwd(p: string, cwd: string) {
return path.isAbsolute(p) ? p : path.resolve(cwd, p);
}

// get import context
export function getImportStr(
from: string,
Expand Down Expand Up @@ -357,17 +346,30 @@ export function extend<T = any>(obj, ...args: Array<Partial<T>>): T {
return obj;
}

// load package.json
export function getPkgInfo(cwd: string) {
const pkgPath = path.resolve(cwd, './package.json');
if (!fs.existsSync(pkgPath)) return {};
try {
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
} catch (e) {
// parse json
export function parseJson(jsonStr: string) {
if (jsonStr) {
try {
return JSON.parse(jsonStr);
} catch (e) {
return {};
}
} else {
return {};
}
}

// load package.json
export function getPkgInfo(cwd: string) {
return readJson(path.resolve(cwd, './package.json'));
}

// read json file
export function readJson(jsonUrl: string) {
if (!fs.existsSync(jsonUrl)) return {};
return parseJson(fs.readFileSync(jsonUrl, 'utf-8'));
}

// format property
export function formatProp(prop: string) {
return prop.replace(/[._-][a-z]/gi, s => s.substring(1).toUpperCase());
Expand Down Expand Up @@ -402,6 +404,20 @@ export function camelProp(
return first + property.substring(1);
}

// load tsconfig.json
export function loadTsConfig(tsconfigPath: string): ts.CompilerOptions {
tsconfigPath = path.extname(tsconfigPath) === '.json' ? tsconfigPath : `${tsconfigPath}.json`;
const tsConfig = readJson(tsconfigPath) as TsConfigJson;
if (tsConfig.extends) {
const extendTsConfig = loadTsConfig(path.resolve(path.dirname(tsconfigPath), tsConfig.extends));
return {
...tsConfig.compilerOptions,
...extendTsConfig,
};
}
return tsConfig.compilerOptions || {};
}

/**
* ts ast utils
*/
Expand Down
4 changes: 2 additions & 2 deletions src/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class Watcher extends EventEmitter {

assert(options.directory, `options.directory must set in ${generatorName}`);
const baseDir = options.directory.replace(/\/|\\/, path.sep);
this.dir = utils.getAbsoluteUrlByCwd(baseDir, this.config.cwd);
this.dir = path.resolve(this.config.cwd, baseDir);
this.dtsDir = path.resolve(
this.config.typings,
path.relative(this.config.cwd, this.dir),
Expand Down Expand Up @@ -162,7 +162,7 @@ export default class Watcher extends EventEmitter {

// on file change
private onChange(filePath: string) {
filePath = utils.getAbsoluteUrlByCwd(filePath, this.dir);
filePath = path.resolve(this.dir, filePath);
debug('file changed %s %o', filePath, this.throttleStack);
if (!this.throttleStack.includes(filePath)) {
this.throttleStack.push(filePath);
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/app/config/config.default.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { PowerPartial, EggAppConfig } from 'egg';
export const view = '123';

export default appInfo => {
const config: any = {};
const config = {} as PowerPartial<EggAppConfig>;

// should change to your own
config.keys = appInfo.name + '_1513135333623_4128';
Expand All @@ -12,4 +13,3 @@ export default appInfo => {

return config;
};

5 changes: 5 additions & 0 deletions test/fixtures/real-js/config/config.local.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = () => {
return {
bb: 123
}
}
9 changes: 9 additions & 0 deletions test/fixtures/real/tsconfig-base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false
}
}
6 changes: 1 addition & 5 deletions test/fixtures/real/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"strict": true,
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"baseUrl": ".",
"paths": {
"egg": [
Expand Down
23 changes: 23 additions & 0 deletions test/generators/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import path from 'path';
import assert = require('assert');
import { GeneratorResult } from '../../dist/';
import * as utils from '../../dist/utils';
import { triggerGenerator } from './utils';
import mm from 'egg-mock';

describe('generators/config.test.ts', () => {
const appDir = path.resolve(__dirname, '../fixtures/app');
Expand All @@ -20,6 +22,7 @@ describe('generators/config.test.ts', () => {
});

it('should works without error with *.ts', () => {
mm(utils, 'loadTsConfig', () => ({ skipLibCheck: true }));
const result = triggerGenerator<GeneratorResult>('config', appDir, undefined, commonConfig);
assert(result.content);
assert(result.content!.includes("import ExportConfigDefault from '../../config/config.default';"));
Expand All @@ -33,7 +36,15 @@ describe('generators/config.test.ts', () => {
assert(result.content!.includes('interface EggAppConfig extends NewEggAppConfig { }\n'));
});

it('should not generate d.ts with export plain object', () => {
const result = triggerGenerator<GeneratorResult>('config', appDir, undefined, commonConfig);
assert(result.content);
assert(result.content!.includes("import ExportConfigDefault from '../../config/config.default';"));
assert(!result.content!.includes("import ExportConfigLocal from '../../config/config.local';"));
});

it('should works without error with file changed', () => {
mm(utils, 'loadTsConfig', () => ({ skipLibCheck: true }));
triggerGenerator<GeneratorResult>('config', appDir, undefined, commonConfig);
const result = triggerGenerator<GeneratorResult>('config', appDir, 'config.default', commonConfig);
assert(result.content);
Expand All @@ -49,6 +60,7 @@ describe('generators/config.test.ts', () => {
});

it('should works while file was not exist', () => {
mm(utils, 'loadTsConfig', () => ({ skipLibCheck: true }));
triggerGenerator<GeneratorResult>('config', appDir, undefined, commonConfig);
const result = triggerGenerator<GeneratorResult>('config', appDir, 'config.xxx', commonConfig);
assert(!result.content!.includes("config.xxx';"));
Expand All @@ -57,14 +69,25 @@ describe('generators/config.test.ts', () => {
});

it('should works while only has config.ts', () => {
mm(utils, 'loadTsConfig', () => ({ skipLibCheck: true }));
const result = triggerGenerator<GeneratorResult>('config', path.resolve(__dirname, '../fixtures/app2'));
assert(result.content);
assert(result.content!.includes("import ExportConfig from '../../config/config';"));
assert(result.content!.includes('type Config = ReturnType<typeof ExportConfig>;'));
assert(result.content!.includes('Config'));
});

it('should works with js project anyway', () => {
const result = triggerGenerator<GeneratorResult>('config', path.resolve(__dirname, '../fixtures/real-js'), undefined, commonConfig);
assert(result.content);
assert(result.content!.includes("import ExportConfigDefault = require('../../config/config.default');"));
assert(result.content!.includes("import ExportConfigLocal = require('../../config/config.local');"));
assert(result.content!.includes('type ConfigDefault = typeof ExportConfigDefault;'));
assert(result.content!.includes('type ConfigLocal = ReturnType<typeof ExportConfigLocal>;'));
});

it('should works without error with empty config.ts', () => {
mm(utils, 'loadTsConfig', () => ({ skipLibCheck: true }));
const result = triggerGenerator<GeneratorResult>('config', path.resolve(__dirname, '../fixtures/app4'));
assert(!result.content);
});
Expand Down

0 comments on commit 368e2a9

Please sign in to comment.