!128 fix: eslint error

Merge pull request !128 from 聪小陈/fix-eslint-error
This commit is contained in:
陈超涛 2024-01-31 01:58:17 +00:00 committed by Gitee
commit 63b82c2632
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
56 changed files with 232 additions and 182 deletions

View File

@ -57,6 +57,7 @@ module.exports = {
'no-constant-condition': 'off',
'no-function-declare-after-return/no-function-declare-after-return': 'error',
'@typescript-eslint/ban-ts-comment': 'warn'
},
globals: {
isDev: true,

View File

@ -3,7 +3,7 @@
**关联的 Issues:** [请列出与此 PR 相关的 issue 编号]
**检查项(无需修改,提交后界面上可勾选):**
- [ ] 代码已经被检视
- [ ] 代码已经被审查
- [ ] 代码符合项目的代码标准和最佳实践
- [ ] 代码已经通过所有测试用例
- [ ] 代码不影响现有功能的正常使用

View File

@ -70,6 +70,7 @@
"jest": "^25.5.4",
"jest-environment-jsdom-sixteen": "^1.0.3",
"lint-staged": "^15.2.0",
"openinula": "workspace:*",
"prettier": "^3.1.1",
"rollup": "^2.75.5",
"rollup-plugin-execute": "^1.1.1",

17
packages/inula-cli/externals.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
/*
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
*
* openInula is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
declare module 'crequire';

View File

@ -57,7 +57,7 @@ export default (api: API) => {
api.applyHook({ name: 'afterStartDevServer' });
});
} else {
api.logger.error("Can't find config");
api.logger.error('Can\'t find config');
}
break;
case 'vite':
@ -70,7 +70,7 @@ export default (api: API) => {
server.printUrls();
});
} else {
api.logger.error("Can't find config");
api.logger.error('Can\'t find config');
}
break;
default:

View File

@ -33,15 +33,17 @@ export default (api: API) => {
args._.shift();
}
if (args._.length === 0) {
api.logger.warn("Can't find any generate options.");
api.logger.warn('Can\'t find any generate options.');
return;
}
switch (args._[0]) {
case 'jest':
args._.shift();
const isESM = api.packageJson['type'] === 'module';
await generateJest(args, api.cwd, isESM);
{
args._.shift();
const isESM = api.packageJson['type'] === 'module';
await generateJest(args, api.cwd, isESM);
}
break;
default:
}
@ -50,7 +52,7 @@ export default (api: API) => {
};
const generateJest = async (args: yargsParser.Arguments, cwd: string, isESM: boolean) => {
let isTs: boolean = false;
let isTs = false;
if (args['ts']) {
isTs = true;
} else {

View File

@ -25,7 +25,7 @@ export default (api: any) => {
initialState: api.userConfig.remoteProxy,
fn: async function (args: any, state: any) {
if (!state) {
api.logger.error(`Invalid proxy config!`);
api.logger.error('Invalid proxy config!');
return;
}
const app = express();

View File

@ -61,7 +61,7 @@ export default async function run() {
break;
}
let enableDebug: boolean = false;
let enableDebug = false;
if (process.env.DEBUG === 'true') {
enableDebug = true;

View File

@ -89,7 +89,7 @@ export default class Config {
getConfigFile(): string | null {
const configFileList: string[] = DEFAULT_CONFIG_FILES.map(f => join(this.cwd, f));
for (let configFile of configFileList) {
for (const configFile of configFileList) {
if (existsSync(configFile)) {
return configFile;
}

View File

@ -46,11 +46,11 @@ export default class Hub {
userConfig: UserConfig = {};
packageJson: PackageJSON;
stage: ServiceStage = ServiceStage.uninitialized;
buildConfig: { name: string; config: object }[] = [];
buildConfig: { name: string; config: Record<string, unknown> }[] = [];
pluginManager: Plugin;
buildConfigPath: BuildConfig[] = [];
devBuildConfig: object = {};
compileMode: string = '';
devBuildConfig: Record<string, unknown> = {};
compileMode = '';
builtInPlugins: string[] = [];
pluginPaths: string[] = [];
devProxy: DevProxy | null = null;
@ -150,14 +150,15 @@ export default class Hub {
async analyzeBuildConfig() {
if (this.userConfig.devBuildConfig) {
let { name, path, env } = this.userConfig.devBuildConfig;
let { path } = this.userConfig.devBuildConfig;
const { env } = this.userConfig.devBuildConfig;
path = isAbsolute(path) ? path : join(process.cwd(), path);
if (!existsSync(path)) {
this.logger.warn(`Cant't find dev build config. Path is ${path}`);
return;
}
this.logger.debug(`Find dev build config. Path is ${path}`);
let bc = await loadModule<object | Function>(path);
const bc = await loadModule<Record<string, unknown> | ((...args: any[]) => any)>(path);
if (bc == undefined) {
return;
}
@ -196,14 +197,15 @@ export default class Hub {
}
this.buildConfigPath.forEach(async config => {
let { name, path } = config;
let { path } = config;
const { name } = config;
path = isAbsolute(path) ? path : join(process.cwd(), path);
if (!existsSync(path)) {
this.logger.debug(`Cant't find build config. Path is ${path}`);
return;
}
this.logger.debug(`Find build config. Path is ${path}`);
let bc = await loadModule<object | Function>(path);
const bc = await loadModule<Record<string, unknown> | ((...args: any[]) => any)>(path);
if (bc == undefined) {
return;
}

View File

@ -36,7 +36,7 @@ export interface IPlugin {
id: string;
key: string;
path: string;
apply: Function;
apply: (...args: any[]) => any;
}
export default class Plugin {
@ -57,7 +57,7 @@ export default class Plugin {
} = {};
hub: Hub;
logger: Logger;
registerFunction: Function[] = [];
registerFunction: ((...args: any[]) => any)[] = [];
// 解决调用this[props]时ts提示属性未知
[key: string]: any;
@ -110,7 +110,7 @@ export default class Plugin {
});
for (const obj of objs) {
const module: Function | undefined = await loadModule(obj.path);
const module: ((...args: any[]) => any) | undefined = await loadModule(obj.path);
if (module) {
try {
module(obj.api);

View File

@ -55,7 +55,7 @@ export default class PluginAPI {
this.register(hook);
}
registerMethod(fn: Function) {
registerMethod(fn: (...args: any[]) => any) {
this.manager.registerFunction.push(fn);
}

View File

@ -19,8 +19,8 @@ import { Logger } from '../utils/logger.js';
import type * as http from 'http';
import type * as express from 'express';
interface Request extends express.Request {}
interface Response extends express.Response {}
type Request = express.Request;
type Response = express.Response;
export interface IDep {
[name: string]: string;
@ -37,7 +37,7 @@ export interface IPlugin {
id: string;
key: string;
path: string;
apply: Function;
apply: (...args: any[]) => any;
config?: IPluginConfig;
isPreset?: boolean;
@ -45,7 +45,7 @@ export interface IPlugin {
export interface IPluginConfig {
default?: any;
onChange?: string | Function;
onChange?: string | ((...args: any[]) => any);
}
export interface IHook {
@ -94,7 +94,7 @@ export interface API {
(hook: IHook): void;
};
registerMethod: {
(method: Function): void;
(method: (...args: any[]) => any): void;
};
applyHook: {
(opts: applyHookConfig): void;
@ -133,8 +133,8 @@ export interface MockConfig {
export interface DevBuildConfig {
name: string;
path: string;
args?: object;
env?: object;
args?: Record<string, unknown>;
env?: Record<string, unknown>;
devProxy?: DevProxy;
}
@ -147,8 +147,8 @@ export interface DevProxy {
export interface BuildConfig {
name: string;
path: string;
args?: object;
env?: object;
args?: Record<string, unknown>;
env?: Record<string, unknown>;
}
export type ExportUserConfig = UserConfig | Promise<UserConfig>;

View File

@ -38,13 +38,15 @@ function getMocksFile() {
const mockFiles = globSync('**/*.js', {
cwd: mockDir,
});
let ret = mockFiles.reduce((mocks: any, mockFile: string) => {
const ret = mockFiles.reduce((mocks: any, mockFile: string) => {
if (!mockFile.startsWith('_')) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const file = require(join(mockDir, mockFile));
mocks = {
...mocks,
...require(join(mockDir, mockFile)),
...file,
};
console.log('mockFile', require(join(mockDir, mockFile)));
console.log('mockFile', file);
}
return mocks;
@ -54,8 +56,8 @@ function getMocksFile() {
}
function generateRoutes(app: any) {
let mockStartIndex = app._router.stack.length,
mocks: Mock = {};
const mockStartIndex = app._router.stack.length;
let mocks: Mock = {};
try {
mocks = getMocksFile();

View File

@ -16,7 +16,6 @@
import { dirname } from 'path';
import { readFileSync, writeFileSync } from 'fs';
import resolve from 'resolve';
// @ts-ignore
import crequire from 'crequire';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

View File

@ -11,9 +11,16 @@
"moduleResolution": "node",
"esModuleInterop": true,
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts", "./src/template/**/*"],
"include": [
"src/**/*",
"./externals.d.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
"./src/template/**/*"
],
"ts-node": {
"esm": true,
},
}
}

View File

@ -7,7 +7,7 @@
"types": "build/@types/index.d.ts",
"scripts": {
"demo-serve": "webpack serve --mode=development",
"rollup-build": "rollup --config rollup.config.js && npm run build-types",
"build": "rollup --config rollup.config.js && npm run build-types",
"build-types": "tsc -p tsconfig.json && rollup -c build-type.js",
"test": "jest --config jest.config.js",
"test-c": "jest --coverage"

View File

@ -117,7 +117,7 @@ export class I18n extends EventDispatcher<Events> {
formatMessage(
id: MessageDescriptor | string,
values: Object | undefined = {},
values: Record<string, unknown> | undefined = {},
{ message, context, formatOptions }: MessageOptions = {}
) {
return getFormatMessage(this, id, values, { message, context, formatOptions });

View File

@ -40,11 +40,11 @@ class Translation {
* @param values
* @param formatOptions
*/
translate(values: object, formatOptions: FormatOptions = {}): string {
translate(values: Record<string, unknown>, formatOptions: FormatOptions = {}): string {
const createTextFormatter = (
locale: Locale,
locales: Locales,
values: object,
values: Record<string, unknown>,
formatOptions: FormatOptions,
localeConfig: LocaleConfig
) => {
@ -66,13 +66,13 @@ class Translation {
return textFormatter;
};
let textFormatter = createTextFormatter(this.locale, this.locales, values, formatOptions, this.localeConfig);
const textFormatter = createTextFormatter(this.locale, this.locales, values, formatOptions, this.localeConfig);
// 通过递归方法formatCore进行格式化处理
const result = this.formatMessage(this.compiledMessage, textFormatter);
return result; // 返回要格式化的结果
}
formatMessage(compiledMessage: CompiledMessage, textFormatter: Function) {
formatMessage(compiledMessage: CompiledMessage, textFormatter: (...args: any[]) => any) {
if (!Array.isArray(compiledMessage)) {
return compiledMessage;
}

View File

@ -23,10 +23,11 @@ import creatI18nCache from './cache/cache';
export function getFormatMessage(
i18n: I18n,
id: MessageDescriptor | string,
values: Object | undefined = {},
values: Record<string, unknown> | undefined = {},
options: MessageOptions = {}
) {
let { message, context, formatOptions } = options;
let { message, context } = options;
const { formatOptions } = options;
const cache = i18n.cache ?? creatI18nCache();
if (typeof id !== 'string') {
values = values || id.defaultValues;

View File

@ -19,19 +19,19 @@ import { LexerInterface } from '../types/interfaces';
class Lexer<T> implements LexerInterface<T> {
readonly startState: string;
readonly states: Record<string, any>;
private buffer: string = '';
private buffer = '';
private stack: string[] = [];
private index: number = 0;
private line: number = 1;
private col: number = 1;
private queuedText: string = '';
private state: string = '';
private index = 0;
private line = 1;
private col = 1;
private queuedText = '';
private state = '';
private groups: string[] = [];
private error: Record<string, any> | undefined;
private regexp;
private fast: object = {};
private fast: Record<string, unknown> = {};
private queuedGroup: string | null = '';
private value: string = '';
private value = '';
constructor(unionReg: Record<string, any>, startState: string) {
this.startState = startState;

View File

@ -14,11 +14,11 @@
*/
const body: Record<string, any> = {
doubleapos: { match: "''", value: () => "'" },
doubleapos: { match: '\'\'', value: () => '\'' },
quoted: {
lineBreaks: true,
match: /'[{}#](?:[^]*?[^'])?'(?!')/u,
value: src => src.slice(1, -1).replace(/''/g, "'"),
value: src => src.slice(1, -1).replace(/''/g, '\''),
},
argument: {
lineBreaks: true,

View File

@ -23,8 +23,8 @@ const defaultErrorRule = ruleUtils.getRuleOptions('error', { lineBreaks: true, s
// 解析规则并生成词法分析器所需的数据结构,以便进行词法分析操作
function parseRules(rules: Record<string, any>, hasStates: boolean): Record<string, any> {
let errorRule: Record<string, any> | null = null;
const fast: object = {};
let enableFast: boolean = true;
const fast: Record<string, unknown> = {};
let enableFast = true;
let unicodeFlag: boolean | null = null;
const groups: Record<string, any>[] = [];
const parts: string[] = [];
@ -148,6 +148,7 @@ function parseMappingRule(mappingRule: Record<string, any>, startState?: string)
}
});
}
// eslint-disable-next-line
rules.splice.apply(rules, splice);
j--;
}
@ -176,7 +177,7 @@ function parseMappingRule(mappingRule: Record<string, any>, startState?: string)
return new Lexer(mappingAllRules, startState);
}
function processFast(match, fast: {}, options) {
function processFast(match, fast: Record<string, unknown>, options) {
while (match.length && typeof match[0] === 'string' && match[0].length === 1) {
const word = match.shift();
fast[word.charCodeAt(0)] = options;

View File

@ -42,7 +42,7 @@ class Parser {
isSelectKeyValid(token: RawToken, type: Select['type'], value: string) {
if (value[0] === '=') {
if (type === 'select') {
throw new Error(`The key value of the select type is invalid.`);
throw new Error('The key value of the select type is invalid.');
}
} else if (type !== 'select') {
const values = type === 'plural' ? this.cardinalKeys : this.ordinalKeys;
@ -132,7 +132,7 @@ class Parser {
if (checkSelectType(nextToken.value.toLowerCase())) {
throw new Error(`Invalid parameter type: ${nextToken.value}`);
}
let param = this.parse(isPlural);
const param = this.parse(isPlural);
return {
type: 'function',

View File

@ -19,7 +19,7 @@ import Lexer from '../parser/Lexer';
// FormattedMessage的参数定义
export interface FormattedMessageProps extends MessageDescriptor {
values?: object;
values?: Record<string, unknown>;
tagName?: string;
children?(nodes: any[]): any;
@ -203,7 +203,7 @@ export interface InjectedIntl {
// 信息格式化
formatMessage(
messageDescriptor: MessageDescriptor,
values?: object,
values?: Record<string, unknown>,
options?: MessageOptions,
useMemorize?: boolean
): string;

View File

@ -32,7 +32,7 @@ export type Locale = string;
export type Locales = Locale | Locale[];
export type LocaleConfig = { plurals?: Function };
export type LocaleConfig = { plurals?: (...arg: any) => any };
export type AllLocaleConfig = Record<Locale, LocaleConfig>;
@ -59,7 +59,7 @@ export type Token = Content | PlainArg | FunctionArg | Select | Octothorpe;
export type DatePool = Date | string;
export type SelectPool = string | object;
export type SelectPool = string | Record<string, unknown>;
export type RawToken = {
type: string;
@ -76,9 +76,9 @@ export type I18nProviderProps = I18nContextProps & configProps;
export type IntlType = {
i18n: I18n;
formatMessage: Function;
formatNumber: Function;
formatDate: Function;
formatMessage: (...args: any[]) => any;
formatNumber: (...args: any[]) => any;
formatDate: (...args: any[]) => any;
};
export interface InjectedIntlProps {

View File

@ -73,7 +73,9 @@ function copyStaticProps<T, U>(targetComponent: T, sourceComponent: U): T {
try {
// 在一个已有的targetComponent对象上增加sourceComponent的属性
Object.defineProperty(targetComponent, key, Object.getOwnPropertyDescriptor(sourceComponent, key)!);
} catch (e) {}
} catch (e) {
console.log('Error occurred while copying static props:', e);
}
}
});

View File

@ -32,12 +32,14 @@ const checkSticky = () => typeof new RegExp('')?.sticky === 'boolean';
// 转义正则表达式中的特殊字符
function transferReg(s: string): string {
// eslint-disable-next-line
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
// 计算正则表达式中捕获组的数量
function getRegGroups(s: string): number {
const re = new RegExp('|' + s);
// eslint-disable-next-line
return re.exec('')?.length! - 1;
}

View File

@ -90,19 +90,19 @@ describe('I18n', () => {
});
it('._ allow escaping syntax characters', () => {
const messages = {
"My ''name'' is '{name}'": "Mi ''nombre'' es '{name}'",
'My \'\'name\'\' is \'{name}\'': 'Mi \'\'nombre\'\' es \'{name}\'',
};
const i18n = new I18n({
locale: 'es',
messages: { es: messages },
});
expect(i18n.formatMessage("My ''name'' is '{name}'")).toEqual("Mi 'nombre' es {name}");
expect(i18n.formatMessage('My \'\'name\'\' is \'{name}\'')).toEqual('Mi \'nombre\' es {name}');
});
it('._ should format message from catalog', function () {
const messages = {
Hello: 'Salut',
id: "Je m'appelle {name}",
id: 'Je m\'appelle {name}',
};
const i18n = new I18n({
locale: 'fr',
@ -110,7 +110,7 @@ describe('I18n', () => {
});
expect(i18n.locale).toEqual('fr');
expect(i18n.formatMessage('Hello')).toEqual('Salut');
expect(i18n.formatMessage('id', { name: 'Fred' })).toEqual("Je m'appelle Fred");
expect(i18n.formatMessage('id', { name: 'Fred' })).toEqual('Je m\'appelle Fred');
});
it('should return the formatted date and time', () => {

View File

@ -43,7 +43,7 @@ describe('eventEmitter', () => {
expect(listener).not.toBeCalled();
});
it("should do nothing when even doesn't exist", () => {
it('should do nothing when even doesn\'t exist', () => {
const unknown = jest.fn();
const emitter = new EventEmitter();

View File

@ -38,7 +38,7 @@ const useIR = <T = unknown>(
Inula.useEffect(() => {
const fetchData = async () => {
try {
let result = await irClient.query(url, config, options);
const result = await irClient.query(url, config, options);
document.addEventListener('request', handleRequest(result));
setData(result); // 未设置轮询查询时展示一次

View File

@ -48,7 +48,7 @@ class InterceptorManager<V> implements IrInterceptorManager<V> {
this.handlers = [];
}
forEach(func: Function) {
forEach(func: (...arg: any) => any) {
utils.forEach(this.handlers, function forEachHandler(h: any) {
if (h !== null) {
func(h);

View File

@ -23,12 +23,10 @@ import CancelError from '../cancel/CancelError';
export const fetchRequest = (config: IrRequestConfig): Promise<IrResponse> => {
return new Promise((resolve, reject) => {
let {
const {
method = 'GET',
baseURL,
url,
params = null,
data = null,
headers = {},
responseType,
timeout = 0,
@ -37,10 +35,10 @@ export const fetchRequest = (config: IrRequestConfig): Promise<IrResponse> => {
withCredentials = false,
onUploadProgress = null,
onDownloadProgress = null,
signal,
} = config;
let { data = null, url, signal } = config;
let controller = new AbortController();
const controller = new AbortController();
if (!signal) {
signal = controller.signal;
}

View File

@ -21,10 +21,9 @@ import { Method, ResponseType } from '../types/types';
export const ieFetchRequest = (config: IrRequestConfig): Promise<IrResponse> => {
return new Promise((resolve, reject) => {
let {
const {
method = 'get',
baseURL,
url,
params = null,
data = null,
headers = {},
@ -34,7 +33,7 @@ export const ieFetchRequest = (config: IrRequestConfig): Promise<IrResponse> =>
cancelToken = null,
withCredentials = false,
} = config;
let { url } = config;
let controller: any;
let signal;

View File

@ -231,7 +231,7 @@ export interface IrInterceptorManager<T> {
clear(): void;
// 过滤跳过迭代器
forEach(func: Function): void;
forEach(func: (...arg: any) => any): void;
}
export interface IrErrorInterface {

View File

@ -39,7 +39,7 @@ export type IrTransformer = (data: any, headers?: IrHeaders) => any;
// Headers
export type HeaderMap = Record<string, string | string[]>;
export type HeaderMatcher = boolean | RegExp | Function;
export type HeaderMatcher = boolean | RegExp | ((...args: any[]) => any);
// Promise 成功和拒绝类型
export type FulfilledFn<T> = (value: T) => T | Promise<T>; // 泛型确保了拦截器链中各个环节之间的一致性,避免数据类型不匹配引发的错误

View File

@ -23,7 +23,7 @@ import { Callback, FilterFunc, PropFilterFunc } from '../../types/types';
*
* @returns {any} func 使 apply() thisArg func
*/
function bind(func: Function, thisArg: any): (...args: any[]) => any {
function bind(func: (...args: any[]) => any, thisArg: any): (...args: any[]) => any {
return (...args: any[]) => func.apply(thisArg, args);
}
@ -107,7 +107,7 @@ const checkHTMLForm = createTypeChecker('HTMLFormElement');
*/
function forEach<T>(
input: T | T[] | Record<string, T> | null | undefined,
func: Function,
func: (...args: any[]) => any,
options: { includeAll?: boolean } = {}
): void {
if (input === null || input === undefined) {
@ -194,7 +194,7 @@ function extendObject(
* @returns {string[]}
*/
function getAllPropertyNames(obj: Record<string, any>): string[] {
let result: string[] = [];
const result: string[] = [];
let currentObj = obj;
while (currentObj) {
const propNames = Object.getOwnPropertyNames(currentObj);
@ -406,6 +406,7 @@ function objectToQueryString(obj: Record<string, any>) {
const all = <T>(promises: Array<Promise<T>>): Promise<T[]> => Promise.all(promises);
function spread<T>(callback: Callback<T>): (arr: any[]) => T {
// eslint-disable-next-line
return (arr: any[]): T => callback.apply(null, arr);
}

View File

@ -17,7 +17,7 @@ import IrHeaders from '../../core/IrHeaders';
import defaultConfig from '../../config/defaultConfig';
import { IrRequestConfig, IrResponse } from '../../types/interfaces';
function transformData(inputConfig: IrRequestConfig, func: Function, response?: IrResponse) {
function transformData(inputConfig: IrRequestConfig, func: (...arg: any) => any, response?: IrResponse) {
const config = inputConfig || defaultConfig;
const context = response || config;
const headers = IrHeaders.from(context.headers);

View File

@ -20,9 +20,9 @@ function convertRawHeaders(rawHeaders: string): HeaderMap {
if (rawHeaders) {
rawHeaders.split('\n').forEach((item: string) => {
let i = item.indexOf(':');
let key = item.substring(0, i).trim().toLowerCase();
let val = item.substring(i + 1).trim();
const i = item.indexOf(':');
const key = item.substring(0, i).trim().toLowerCase();
const val = item.substring(i + 1).trim();
if (!key || (convertedHeaders[key] && key !== 'set-cookie')) {
return;

View File

@ -38,7 +38,7 @@ function processValueByParser(key: string, value: any, parser?: HeaderMatcher):
return parseKeyValuePairs(value);
}
if (utils.checkFunction(parser)) {
return (parser as Function)(value, key);
return (parser as (value: any, key: string) => any)(value, key);
}
if (utils.checkRegExp(parser)) {
return (parser as RegExp).exec(value)?.filter(item => item !== undefined);

View File

@ -11,7 +11,10 @@
"declaration": true,
"moduleResolution": "node",
"sourceMap": true,
"downlevelIteration": true
"downlevelIteration": true,
"paths": {
"openinula": ["../packages/inula"],
}
},
"include": [
"./src/**/*",

View File

@ -46,7 +46,6 @@
"@babel/plugin-transform-template-literals": "7.16.7",
"@babel/preset-env": "7.16.7",
"@babel/preset-typescript": "^7.16.7",
"openinula": "^0.0.1",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
"@testing-library/jest-dom": "^5.16.5",

View File

@ -18,14 +18,14 @@ import { createPath } from '../utils';
describe('createPath', () => {
describe('given only a pathname', () => {
it('returns the pathname unchanged', () => {
let path = createPath({ pathname: 'https://google.com' });
const path = createPath({ pathname: 'https://google.com' });
expect(path).toBe('https://google.com');
});
});
describe('given a pathname and a search param', () => {
it('returns the constructed pathname', () => {
let path = createPath({
const path = createPath({
pathname: 'https://google.com',
search: '?something=cool',
});
@ -35,7 +35,7 @@ describe('createPath', () => {
describe('given a pathname and a search param without ?', () => {
it('returns the constructed pathname', () => {
let path = createPath({
const path = createPath({
pathname: 'https://google.com',
search: 'something=cool',
});
@ -45,7 +45,7 @@ describe('createPath', () => {
describe('given a pathname and a hash param', () => {
it('returns the constructed pathname', () => {
let path = createPath({
const path = createPath({
pathname: 'https://google.com',
hash: '#section-1',
});
@ -55,7 +55,7 @@ describe('createPath', () => {
describe('given a pathname and a hash param without #', () => {
it('returns the constructed pathname', () => {
let path = createPath({
const path = createPath({
pathname: 'https://google.com',
hash: 'section-1',
});
@ -65,7 +65,7 @@ describe('createPath', () => {
describe('given a full location object', () => {
it('returns the constructed pathname', () => {
let path = createPath({
const path = createPath({
pathname: 'https://google.com',
search: 'something=cool',
hash: '#section-1',

View File

@ -29,7 +29,7 @@ function useLocation() {
return useContext(RouterContext).location;
}
function useParams<P>(): Params<P> | {};
function useParams<P>(): Params<P> | Record<string, unknown>;
function useParams() {
const match = useContext(RouterContext).match;
return match ? match.params : {};

View File

@ -38,9 +38,9 @@ describe('path lexer Test', () => {
expect(tokens).toStrictEqual([{ type: 'delimiter', value: '/' }]);
});
it(`don't start with a slash`, () => {
it('don\'t start with a slash', () => {
const func = () => lexer('abc.com');
expect(func).toThrow(Error(`Url must start with "/".`));
expect(func).toThrow(Error('Url must start with "/".'));
});
it('dynamic params test', () => {

View File

@ -26,9 +26,9 @@ export function lexer(path: string): Token[] {
return tokens;
}
let urlPath = cleanPath(path);
const urlPath = cleanPath(path);
if (urlPath !== '*' && !urlPath.startsWith('/')) {
throw new Error(`Url must start with "/".`);
throw new Error('Url must start with "/".');
}
const getLiteral = () => {

View File

@ -84,7 +84,7 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
* @param currentIdx
*/
const lookToNextDelimiter = (currentIdx: number): boolean => {
let hasOptionalParam: boolean = false;
let hasOptionalParam = false;
while (currentIdx < tokens.length && tokens[currentIdx].type !== TokenType.Delimiter) {
if (tokens[currentIdx].value === '?' || tokens[currentIdx].value === '*') {
hasOptionalParam = true;
@ -98,8 +98,10 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
const nextToken = tokens[tokenIdx + 1];
switch (token.type) {
case TokenType.Delimiter:
const hasOptional = lookToNextDelimiter(tokenIdx + 1);
pattern += `/${hasOptional ? '?' : ''}`;
{
const hasOptional = lookToNextDelimiter(tokenIdx + 1);
pattern += `/${hasOptional ? '?' : ''}`;
}
break;
case TokenType.Static:
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
@ -111,28 +113,30 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
scores.push(MatchScore.static);
break;
case TokenType.Param:
// 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式
let paramRegexp = '';
if (nextToken) {
switch (nextToken.type) {
case TokenType.LBracket:
// 跳过当前Token和左括号
tokenIdx += 2;
while (tokens[tokenIdx].type !== TokenType.RBracket) {
paramRegexp += tokens[tokenIdx].value;
{
// 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式
let paramRegexp = '';
if (nextToken) {
switch (nextToken.type) {
case TokenType.LBracket:
// 跳过当前Token和左括号
tokenIdx += 2;
while (tokens[tokenIdx].type !== TokenType.RBracket) {
paramRegexp += tokens[tokenIdx].value;
tokenIdx++;
}
paramRegexp = `(${paramRegexp})`;
break;
case TokenType.Pattern:
tokenIdx++;
}
paramRegexp = `(${paramRegexp})`;
break;
case TokenType.Pattern:
tokenIdx++;
paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`;
break;
paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`;
break;
}
}
pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`;
keys.push(token.value);
scores.push(MatchScore.param);
}
pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`;
keys.push(token.value);
scores.push(MatchScore.param);
break;
case TokenType.WildCard:
keys.push(token.value);
@ -167,13 +171,13 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
return null;
}
const matchedPath = reMatch[0];
let params: Params<P> = {};
let parseScore: number[] = Array.from(scores);
const params: Params<P> = {};
const parseScore: number[] = Array.from(scores);
for (let i = 1; i < reMatch.length; i++) {
let param = reMatch[i];
let key = keys[i - 1];
const param = reMatch[i];
const key = keys[i - 1];
if (key === '*' && param) {
let value = param.split('/');
const value = param.split('/');
if (!Array.isArray(params['*'])) {
params['*'] = value;
} else {
@ -212,11 +216,13 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
path += params[token.value];
break;
case TokenType.WildCard:
let wildCard = params['*'];
if (wildCard instanceof Array) {
path += wildCard.join('/');
} else {
path += wildCard;
{
const wildCard = params['*'];
if (wildCard instanceof Array) {
path += wildCard.join('/');
} else {
path += wildCard;
}
}
break;
case TokenType.Delimiter:
@ -250,7 +256,7 @@ export function matchPath<P = any>(
const patterns = Array.isArray(pattern) ? [...pattern] : [pattern];
const matchedResults: Matched<P>[] = [];
for (const item of patterns) {
const parser = createPathParser(item, option);
const parser = createPathParser<P>(item, option);
const matched = parser.parse(pathname);
if (matched) {
matchedResults.push(matched);

View File

@ -56,7 +56,7 @@ type ParseParam<Param extends string> = Param extends `:${infer R}`
? {
[K in R]: string;
}
: {};
: Record<string, unknown>;
type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = {
readonly [Key in keyof OneParam | keyof OtherParam]?: string;

View File

@ -26,10 +26,10 @@
"types": "./build/@types/index.d.ts",
"exports": {
".": {
"default": "./index.js"
"default": "./build/index.js"
},
"./package.json":"./package.json",
"./jsx-runtime": "./jsx-runtime.js",
"./jsx-dev-runtime": "./jsx-dev-runtime.js"
"./jsx-runtime": "./build/jsx-runtime.js",
"./jsx-dev-runtime": "./build/jsx-dev-runtime.js"
}
}

View File

@ -20,10 +20,10 @@ import { createWeakMapProxy } from './WeakMapProxy';
import { createMapProxy } from './MapProxy';
export function createCollectionProxy(
rawObj: Object,
rawObj: Record<string, unknown>,
listener: { current: (...args) => any },
hookObserver = true
): Object {
): ProxyHandler<Record<string, unknown>> {
if (isWeakSet(rawObj)) {
return createWeakSetProxy(rawObj, listener, hookObserver);
}

View File

@ -311,7 +311,7 @@ export function createStore<S extends Record<string, any>, A extends UserActions
}
// 函数组件中使用的hook
export function useStore<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>>(
export function useStore<S extends Record<string, unknown>, A extends UserActions<S>, C extends UserComputedValues<S>>(
id: string
): StoreObj<S, A, C> {
const storeObj = storeMap.get(id);

View File

@ -31,7 +31,7 @@ export interface IObserver {
clearByVNode: (vNode: any) => void;
}
export type StoreConfig<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
export type StoreConfig<S extends Record<string, unknown>, A extends UserActions<S>, C extends UserComputedValues<S>> = {
id?: string;
state?: S;
actions?: A;
@ -41,22 +41,22 @@ export type StoreConfig<S extends object, A extends UserActions<S>, C extends Us
};
};
export type UserActions<S extends object> = {
export type UserActions<S extends Record<string, unknown>> = {
[K: string]: ActionFunction<S>;
};
export type ActionFunction<S extends object> = (this: StoreObj<S, any, any>, state: S, ...args: any[]) => any;
export type ActionFunction<S extends Record<string, unknown>> = (this: StoreObj<S, any, any>, state: S, ...args: any[]) => any;
export type StoreActions<S extends object, A extends UserActions<S>> = {
export type StoreActions<S extends Record<string, unknown>, A extends UserActions<S>> = {
[K in keyof A]: Action<A[K], S>;
};
type Action<T extends ActionFunction<any>, S extends object> = (
type Action<T extends ActionFunction<any>, S extends Record<string, unknown>> = (
this: StoreObj<S, any, any>,
...args: RemoveFirstFromTuple<Parameters<T>>
) => ReturnType<T>;
export type StoreObj<S extends object, A extends UserActions<S>, C extends UserComputedValues<S>> = {
export type StoreObj<S extends Record<string, unknown>, A extends UserActions<S>, C extends UserComputedValues<S>> = {
$s: S;
$a: StoreActions<S, A>;
$c: UserComputedValues<S>;
@ -66,7 +66,7 @@ export type StoreObj<S extends object, A extends UserActions<S>, C extends UserC
$unsubscribe: (listener: (mutation) => void) => void;
} & { [K in keyof S]: S[K] } & { [K in keyof A]: Action<A[K], S> } & { [K in keyof C]: ReturnType<C[K]> };
export type PlannedAction<S extends object, F extends ActionFunction<S>> = {
export type PlannedAction<S extends Record<string, unknown>, F extends ActionFunction<S>> = {
action: string;
payload: any[];
resolve: ReturnType<F>;
@ -78,21 +78,21 @@ type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
? I
: [];
export type UserComputedValues<S extends object> = {
export type UserComputedValues<S extends Record<string, unknown>> = {
[K: string]: ComputedFunction<S>;
};
type ComputedFunction<S extends object> = (state: S) => any;
type ComputedFunction<S extends Record<string, unknown>> = (state: S) => any;
export type AsyncAction<T extends ActionFunction<any>, S extends object> = (
export type AsyncAction<T extends ActionFunction<any>, S extends Record<string, unknown>> = (
this: StoreObj<S, any, any>,
...args: RemoveFirstFromTuple<Parameters<T>>
) => Promise<ReturnType<T>>;
export type QueuedStoreActions<S extends object, A extends UserActions<S>> = {
export type QueuedStoreActions<S extends Record<string, unknown>, A extends UserActions<S>> = {
[K in keyof A]: AsyncAction<A[K], S>;
};
export type ComputedValues<S extends object, C extends UserComputedValues<S>> = {
export type ComputedValues<S extends Record<string, unknown>, C extends UserComputedValues<S>> = {
[K in keyof C]: ReturnType<C[K]>;
};

View File

@ -105,25 +105,27 @@ export function handleRenderThrowError(sourceVNode: VNode, error: any) {
return;
}
case ClassComponent:
const ctor = vNode.type;
const instance = vNode.realNode;
if (
(vNode.flags & DidCapture) === InitFlag &&
(typeof ctor.getDerivedStateFromError === 'function' ||
(instance !== null && typeof instance.componentDidCatch === 'function'))
) {
FlagUtils.markShouldCapture(vNode);
{
const ctor = vNode.type;
const instance = vNode.realNode;
if (
(vNode.flags & DidCapture) === InitFlag &&
(typeof ctor.getDerivedStateFromError === 'function' ||
(instance !== null && typeof instance.componentDidCatch === 'function'))
) {
FlagUtils.markShouldCapture(vNode);
// Class捕捉到异常触发一次刷新
const update = createClassErrorUpdate(vNode, error);
pushUpdate(vNode, update);
// Class捕捉到异常触发一次刷新
const update = createClassErrorUpdate(vNode, error);
pushUpdate(vNode, update);
launchUpdateFromVNode(vNode);
launchUpdateFromVNode(vNode);
// 有异常处理类把抛出异常的节点的Interrupted标志去掉继续走正常的绘制流程
FlagUtils.removeFlag(sourceVNode, Interrupted);
// 有异常处理类把抛出异常的节点的Interrupted标志去掉继续走正常的绘制流程
FlagUtils.removeFlag(sourceVNode, Interrupted);
return;
return;
}
}
break;
default:

View File

@ -53,19 +53,25 @@ export function pushUpdate(vNode: VNode, update: Update) {
// 根据update获取新的state
function calcState(vNode: VNode, update: Update, inst: any, oldState: any, props: any): any {
switch (update.type) {
case UpdateState.Override:
case UpdateState.Override: {
const content = update.content;
return typeof content === 'function' ? content.call(inst, oldState, props) : content;
}
case UpdateState.ForceUpdate:
vNode.isForceUpdate = true;
return oldState;
case UpdateState.Error:
case UpdateState.Error: {
FlagUtils.removeFlag(vNode, ShouldCapture);
FlagUtils.markDidCapture(vNode);
case UpdateState.Update:
const updateContent = update.content;
const newState = typeof updateContent === 'function' ? updateContent.call(inst, oldState, props) : updateContent;
return newState === null || newState === undefined ? oldState : { ...oldState, ...newState };
}
case UpdateState.Update: {
const updateContent = update.content;
const newState = typeof updateContent === 'function' ? updateContent.call(inst, oldState, props) : updateContent;
return newState === null || newState === undefined ? oldState : { ...oldState, ...newState };
}
default:
return oldState;
}

View File

@ -40,8 +40,6 @@ function callRenderQueue() {
}
renderQueue = null;
} catch (error) {
throw error;
} finally {
isCallingRenderQueue = false;
}

View File

@ -60,10 +60,11 @@ function newVirtualNode(tag: VNodeTag, key?: null | string, vNodeProps?: any, re
return new VNode(tag, vNodeProps, key as null | string, realNode);
}
function isClassComponent(comp: Function) {
function isClassComponent(comp: (...arg: any) => any) {
// 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件
// 如果使用静态属性,部分函数高阶组件会将类组件的静态属性复制到自身,导致误判为类组件
// 既然已经兼容使用了该标识符,那么继续使用
// @ts-ignore
return comp.prototype?.isReactComponent === true;
}