fix(reactivity): fix for dependency

This commit is contained in:
Hoikan 2024-05-10 16:54:38 +08:00
parent 601381032d
commit 4ca2d66fac
11 changed files with 99 additions and 223 deletions

View File

@ -53,9 +53,9 @@ export function functionalMacroAnalyze(): Visitor {
const fnNode = extractFnFromMacro(expression, WATCH); const fnNode = extractFnFromMacro(expression, WATCH);
const deps = getWatchDeps(expression); const deps = getWatchDeps(expression);
const depBits = getDependenciesFromNode(deps ?? fnNode, ctx); const depMask = getDependenciesFromNode(deps ?? fnNode, ctx);
addWatch(ctx.current, fnNode, deps); addWatch(ctx.current, fnNode, depMask);
return; return;
} }
} }

View File

@ -64,18 +64,6 @@ export function addSubComponent(comp: ComponentNode, subComp: ComponentNode) {
comp.variables.push({ ...subComp, type: 'subComp' }); comp.variables.push({ ...subComp, type: 'subComp' });
} }
export function addProp(
comp: ComponentNode,
type: PropType,
key: string,
defaultVal: t.Expression | null = null,
alias: string | null = null,
nestedProps: string[] | null = null,
nestedRelationship: t.ObjectPattern | t.ArrayPattern | null = null
) {
comp.props.push({ name: key, type, default: defaultVal, alias, nestedProps, nestedRelationship });
}
export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t.BlockStatement) { export function addLifecycle(comp: ComponentNode, lifeCycle: LifeCycle, block: t.BlockStatement) {
const compLifecycle = comp.lifecycle; const compLifecycle = comp.lifecycle;
if (!compLifecycle[lifeCycle]) { if (!compLifecycle[lifeCycle]) {

View File

@ -1,6 +1,5 @@
import { type NodePath } from '@babel/core'; import { type NodePath } from '@babel/core';
import { AnalyzeContext, Visitor } from './types'; import { AnalyzeContext, Visitor } from './types';
import { addProp } from './nodeFactory';
import { PropType } from '../constants'; import { PropType } from '../constants';
import { types as t } from '@openinula/babel-api'; import { types as t } from '@openinula/babel-api';

View File

@ -45,4 +45,19 @@ describe('viewAnalyze', () => {
expect(inputSecondExp.content.depMask).toEqual(0b1101); expect(inputSecondExp.content.depMask).toEqual(0b1101);
expect(genCode(inputSecondExp.content.dependenciesNode)).toMatchInlineSnapshot('"[doubleCount]"'); expect(genCode(inputSecondExp.content.dependenciesNode)).toMatchInlineSnapshot('"[doubleCount]"');
}); });
it('should analyze object state', () => {
const root = analyze(/*js*/ `
Component(({}) => {
const info = {
firstName: 'John',
lastName: 'Doe'
}
return <h1>{info.firstName}</h1>;
});
`);
const div = root.children![0] as any;
expect(div.children[0].content.depMask).toEqual(0b1);
expect(genCode(div.children[0].content.dependenciesNode)).toMatchInlineSnapshot(`"[info?.firstName]"`);
});
}); });

View File

@ -1,13 +1,41 @@
import { functionalMacroAnalyze } from '../../src/analyzer/functionalMacroAnalyze'; import { functionalMacroAnalyze } from '../../src/analyzer/functionalMacroAnalyze';
import { genCode, mockAnalyze } from '../mock'; import { genCode, mockAnalyze } from '../mock';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { variablesAnalyze } from '../../src/analyzer/variablesAnalyze';
const analyze = (code: string) => mockAnalyze(code, [functionalMacroAnalyze]); const analyze = (code: string) => mockAnalyze(code, [functionalMacroAnalyze, variablesAnalyze]);
describe('watchAnalyze', () => { describe('watchAnalyze', () => {
it('should analyze watch expressions', () => { it('should analyze watch expressions', () => {
const root = analyze(/*js*/ ` const root = analyze(/*js*/ `
Comp(() => { Comp(() => {
let a = 0;
let b = 0;
watch(() => {
console.log(a, b);
});
})
`);
expect(root.watch).toHaveLength(1);
if (!root?.watch?.[0].callback) {
throw new Error('watch callback not found');
}
expect(genCode(root.watch[0].callback.node)).toMatchInlineSnapshot(`
"() => {
console.log(a, b);
}"
`);
if (!root.watch[0].depMask) {
throw new Error('watch deps not found');
}
expect(root.watch[0].depMask).toBe(0b11);
});
it('should analyze watch expressions with dependency array', () => {
const root = analyze(/*js*/ `
Comp(() => {
let a = 0;
let b = 0;
watch(() => { watch(() => {
// watch expression // watch expression
}, [a, b]); }, [a, b]);
@ -25,6 +53,6 @@ describe('watchAnalyze', () => {
if (!root.watch[0].depMask) { if (!root.watch[0].depMask) {
throw new Error('watch deps not found'); throw new Error('watch deps not found');
} }
expect(genCode(root.watch[0].depMask)).toMatchInlineSnapshot('"[a, b]"'); expect(root.watch[0].depMask).toBe(0b11);
}); });
}); });

View File

@ -20,9 +20,5 @@ export function parseReactivity(viewUnits: ViewUnit[], config: ReactivityParserC
}); });
return [dlParticles, usedProperties]; return [dlParticles, usedProperties];
} }
/**
* The key to get the previous map in DependencyMap Chain
*/
export const PrevMap = Symbol('prevMap');
export type * from './types'; export type * from './types';

View File

@ -12,7 +12,7 @@ import {
type ForParticle, type ForParticle,
type IfParticle, type IfParticle,
type EnvParticle, type EnvParticle,
ReactiveBitMap, DepMaskMap,
} from './types'; } from './types';
import { type NodePath, type types as t, type traverse } from '@babel/core'; import { type NodePath, type types as t, type traverse } from '@babel/core';
import { import {
@ -27,7 +27,6 @@ import {
type ExpUnit, type ExpUnit,
} from '@openinula/jsx-view-parser'; } from '@openinula/jsx-view-parser';
import { DLError } from './error'; import { DLError } from './error';
import { PrevMap } from '.';
export class ReactivityParser { export class ReactivityParser {
private readonly config: ReactivityParserConfig; private readonly config: ReactivityParserConfig;
@ -35,10 +34,8 @@ export class ReactivityParser {
private readonly t: typeof t; private readonly t: typeof t;
private readonly traverse: typeof traverse; private readonly traverse: typeof traverse;
private readonly availableProperties: string[]; private readonly availableProperties: string[];
private readonly availableIdentifiers?: string[]; private readonly depMaskMap: DepMaskMap;
private readonly reactiveBitMap: ReactiveBitMap;
private readonly identifierDepMap: Record<string, string[]>; private readonly identifierDepMap: Record<string, string[]>;
private readonly dependencyParseType;
private readonly reactivityFuncNames; private readonly reactivityFuncNames;
private readonly escapeNamings = ['escape', '$']; private readonly escapeNamings = ['escape', '$'];
@ -69,10 +66,7 @@ export class ReactivityParser {
this.t = config.babelApi.types; this.t = config.babelApi.types;
this.traverse = config.babelApi.traverse; this.traverse = config.babelApi.traverse;
this.availableProperties = config.availableProperties; this.availableProperties = config.availableProperties;
this.availableIdentifiers = config.availableIdentifiers; this.depMaskMap = config.depMaskMap;
this.reactiveBitMap = config.reactiveBitMap;
this.identifierDepMap = config.identifierDepMap ?? {};
this.dependencyParseType = config.dependencyParseType ?? 'property';
this.reactivityFuncNames = config.reactivityFuncNames ?? []; this.reactivityFuncNames = config.reactivityFuncNames ?? [];
} }
@ -139,9 +133,7 @@ export class ReactivityParser {
key, key,
{ {
...prop, ...prop,
dependencyIndexArr: [],
dependenciesNode: this.t.arrayExpression([]), dependenciesNode: this.t.arrayExpression([]),
dynamic: false,
}, },
]); ]);
@ -242,7 +234,6 @@ export class ReactivityParser {
value: child.content, value: child.content,
depMask: 0, depMask: 0,
dependenciesNode: this.t.arrayExpression([]), dependenciesNode: this.t.arrayExpression([]),
dynamic: false,
}); });
} }
}); });
@ -280,8 +271,6 @@ export class ReactivityParser {
* @returns ExpParticle | HTMLParticle * @returns ExpParticle | HTMLParticle
*/ */
private parseHTML(htmlUnit: HTMLUnit): ExpParticle | HTMLParticle { private parseHTML(htmlUnit: HTMLUnit): ExpParticle | HTMLParticle {
const { depMask, dependenciesNode, dynamic } = this.getDependencies(htmlUnit.tag);
const innerHTMLParticle: HTMLParticle = { const innerHTMLParticle: HTMLParticle = {
type: 'html', type: 'html',
tag: htmlUnit.tag, tag: htmlUnit.tag,
@ -296,22 +285,7 @@ export class ReactivityParser {
innerHTMLParticle.children = htmlUnit.children.map(this.parseViewParticle.bind(this)); innerHTMLParticle.children = htmlUnit.children.map(this.parseViewParticle.bind(this));
// ---- Not a dynamic tag // ---- Not a dynamic tag
if (!dynamic) return innerHTMLParticle; return innerHTMLParticle;
// ---- Dynamic tag, wrap it in an ExpParticle to make the tag reactive
const id = this.uid();
return {
type: 'exp',
content: {
value: this.t.stringLiteral(id),
viewPropMap: {
[id]: [innerHTMLParticle],
},
depMask: depMask,
dependenciesNode,
},
props: {},
};
} }
// ---- @Comp ---- // ---- @Comp ----
@ -322,9 +296,7 @@ export class ReactivityParser {
* @param compUnit * @param compUnit
* @returns CompParticle | ExpParticle * @returns CompParticle | ExpParticle
*/ */
private parseComp(compUnit: CompUnit): CompParticle | ExpParticle { private parseComp(compUnit: CompUnit): CompParticle {
const { depMask, dependenciesNode, dynamic } = this.getDependencies(compUnit.tag);
const compParticle: CompParticle = { const compParticle: CompParticle = {
type: 'comp', type: 'comp',
tag: compUnit.tag, tag: compUnit.tag,
@ -337,22 +309,7 @@ export class ReactivityParser {
); );
compParticle.children = compUnit.children.map(this.parseViewParticle.bind(this)); compParticle.children = compUnit.children.map(this.parseViewParticle.bind(this));
if (!dynamic) return compParticle; return compParticle;
const id = this.uid();
return {
type: 'exp',
content: {
value: this.t.stringLiteral(id),
viewPropMap: {
[id]: [compParticle],
},
depMask: depMask,
dependenciesNode,
dynamic,
},
props: {},
};
} }
// ---- @For ---- // ---- @For ----
@ -364,25 +321,25 @@ export class ReactivityParser {
*/ */
private parseFor(forUnit: ForUnit): ForParticle { private parseFor(forUnit: ForUnit): ForParticle {
const { depMask, dependenciesNode } = this.getDependencies(forUnit.array); const { depMask, dependenciesNode } = this.getDependencies(forUnit.array);
const prevIdentifierDepMap = this.config.identifierDepMap; const prevIdentifierDepMap = this.config.depMaskMap;
// ---- Find all the identifiers in the key and remove them from the identifierDepMap // ---- Find all the identifiers in the key and remove them from the identifierDepMap
// because once the key is changed, that identifier related dependencies will be changed too, // because once the key is changed, that identifier related dependencies will be changed too,
// so no need to update them // so no need to update them
const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name; const keyDep = this.t.isIdentifier(forUnit.key) && forUnit.key.name;
// ---- Generate an identifierDepMap to track identifiers in item and make them reactive // ---- Generate an identifierDepMap to track identifiers in item and make them reactive
// based on the dependencies from the array // based on the dependencies from the array
this.config.identifierDepMap = Object.fromEntries( this.config.depMaskMap = new Map([
this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([]))) ...this.config.depMaskMap,
...this.getIdentifiers(this.t.assignmentExpression('=', forUnit.item, this.t.objectExpression([])))
.filter(id => !keyDep || id !== keyDep) .filter(id => !keyDep || id !== keyDep)
.map(id => [id, depMask.map(n => this.availableProperties[n])]) .map(id => [id, depMask]),
); ]);
const forParticle: ForParticle = { const forParticle: ForParticle = {
type: 'for', type: 'for',
item: forUnit.item, item: forUnit.item,
array: { array: {
value: forUnit.array, value: forUnit.array,
dynamic,
depMask: depMask, depMask: depMask,
dependenciesNode, dependenciesNode,
}, },
@ -473,13 +430,11 @@ export class ReactivityParser {
* @returns dependency index array * @returns dependency index array
*/ */
private getDependencies(node: t.Expression | t.Statement): { private getDependencies(node: t.Expression | t.Statement): {
dynamic: boolean;
depMask: number; depMask: number;
dependenciesNode: t.ArrayExpression; dependenciesNode: t.ArrayExpression;
} { } {
if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) { if (this.t.isFunctionExpression(node) || this.t.isArrowFunctionExpression(node)) {
return { return {
dynamic: false,
depMask: 0, depMask: 0,
dependenciesNode: this.t.arrayExpression([]), dependenciesNode: this.t.arrayExpression([]),
}; };
@ -492,66 +447,11 @@ export class ReactivityParser {
const depNodes = [...propertyDepNodes] as t.Expression[]; const depNodes = [...propertyDepNodes] as t.Expression[];
return { return {
dynamic: depNodes.length > 0 || !!deps,
depMask: deps, depMask: deps,
dependenciesNode: this.t.arrayExpression(depNodes), dependenciesNode: this.t.arrayExpression(depNodes),
}; };
} }
/**
* @brief Get all the dependencies of a node if a property is a valid dependency as
* 1. the identifier is in the availableProperties
* 2. the identifier is a stand alone identifier
* 3. the identifier is not in an escape function
* 4. the identifier is not in a manual function
* 5. the identifier is not the left side of an assignment expression, which is an assignment expression
* 6. the identifier is not the right side of an assignment expression, which is an update expression
* @param node
* @returns dependency index array
*/
private getIdentifierDependencies(node: t.Expression | t.Statement): [number[], t.Node[]] {
const availableIdentifiers = this.availableIdentifiers ?? this.availableProperties;
const deps = new Set<string>();
const assignDeps = new Set<string>();
const depNodes: Record<string, t.Node[]> = {};
const wrappedNode = this.valueWrapper(node);
this.traverse(wrappedNode, {
Identifier: innerPath => {
const identifier = innerPath.node;
const idName = identifier.name;
if (!availableIdentifiers.includes(idName)) return;
if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) {
assignDeps.add(idName);
} else if (
this.isStandAloneIdentifier(innerPath) &&
!this.isMemberInEscapeFunction(innerPath) &&
!this.isMemberInManualFunction(innerPath)
) {
deps.add(idName);
this.reactiveBitMap[idName]?.forEach(deps.add.bind(deps));
if (!depNodes[idName]) depNodes[idName] = [];
depNodes[idName].push(this.geneDependencyNode(innerPath));
}
},
});
assignDeps.forEach(dep => {
deps.delete(dep);
delete depNodes[dep];
});
let dependencyNodes = Object.values(depNodes).flat();
// ---- deduplicate the dependency nodes
dependencyNodes = dependencyNodes.filter((n, i) => {
const idx = dependencyNodes.findIndex(m => this.t.isNodesEquivalent(m, n));
return idx === i;
});
deps.forEach(this.usedProperties.add.bind(this.usedProperties));
return [[...deps].map(dep => this.availableProperties.lastIndexOf(dep)), dependencyNodes];
}
/** /**
* @brief Get all the dependencies of a node if a member expression is a valid dependency as * @brief Get all the dependencies of a node if a member expression is a valid dependency as
* 1. the property is in the availableProperties * 1. the property is in the availableProperties
@ -575,7 +475,7 @@ export class ReactivityParser {
this.traverse(wrappedNode, { this.traverse(wrappedNode, {
Identifier: innerPath => { Identifier: innerPath => {
const propertyKey = innerPath.node.name; const propertyKey = innerPath.node.name;
const reactiveBitmap = this.reactiveBitMap.get(propertyKey); const reactiveBitmap = this.depMaskMap.get(propertyKey);
if (reactiveBitmap !== undefined) { if (reactiveBitmap !== undefined) {
if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) { if (this.isAssignmentExpressionLeft(innerPath) || this.isAssignmentFunction(innerPath)) {
@ -585,7 +485,7 @@ export class ReactivityParser {
deps.add(propertyKey); deps.add(propertyKey);
if (!depNodes[propertyKey]) depNodes[propertyKey] = []; if (!depNodes[propertyKey]) depNodes[propertyKey] = [];
depNodes[propertyKey].push(this.t.cloneNode(innerPath.node)); depNodes[propertyKey].push(this.geneDependencyNode(innerPath));
} }
} }
}, },

View File

@ -6,66 +6,63 @@ describe('Dependency', () => {
it('should parse the correct dependency', () => { it('should parse the correct dependency', () => {
const viewParticles = parse('Comp(flag)'); const viewParticles = parse('Comp(flag)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toContain(0); expect(content?.depMask).toEqual(0b1);
}); });
it('should parse the correct dependency when interfacing the dependency chain', () => { it('should parse the correct dependency when interfacing the dependency chain', () => {
const viewParticles = parse('Comp(doubleCount)'); const viewParticles = parse('Comp(doubleCount)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
const dependency = content?.dependencyIndexArr; const dependency = content?.depMask;
// ---- doubleCount depends on count, count depends on flag // ---- doubleCount depends on count, count depends on flag
// so doubleCount depends on flag, count and doubleCount // so doubleCount depends on flag, count and doubleCount
expect(dependency).toContain(availableProperties.indexOf('flag')); expect(dependency).toEqual(0b111);
expect(dependency).toContain(availableProperties.indexOf('count'));
expect(dependency).toContain(availableProperties.indexOf('doubleCount'));
}); });
it('should not parse the dependency if the property is not in the availableProperties', () => { it('should not parse the dependency if the property is not in the availableProperties', () => {
const viewParticles = parse('Comp(notExist)'); const viewParticles = parse('Comp(notExist)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
}); });
it('should not parse the dependency if the member expression is in an escaped function', () => { it.skip('should not parse the dependency if the member expression is in an escaped function', () => {
let viewParticles = parse('Comp(escape(flag))'); let viewParticles = parse('Comp(escape(flag))');
let content = (viewParticles[0] as CompParticle).props._$content; let content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
viewParticles = parse('Comp($(flag))'); viewParticles = parse('Comp($(flag))');
content = (viewParticles[0] as CompParticle).props._$content; content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
}); });
it('should not parse the dependency if the member expression is in a manual function', () => { it.skip('should not parse the dependency if the member expression is in a manual function', () => {
const viewParticles = parse('Comp(manual(() => count, []))'); const viewParticles = parse('Comp(manual(() => count, []))');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
}); });
it("should parse the dependencies in manual function's second parameter", () => { it.skip("should parse the dependencies in manual function's second parameter", () => {
const viewParticles = parse('Comp(manual(() => {let a = count}, [flag]))'); const viewParticles = parse('Comp(manual(() => {let a = count}, [flag]))');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(1); expect(content?.depMask).toEqual(1);
}); });
it('should not parse the dependency if the member expression is the left side of an assignment expression', () => { it('should not parse the dependency if the member expression is the left side of an assignment expression', () => {
const viewParticles = parse('Comp(flag = 1)'); const viewParticles = parse('Comp(flag = 1)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
}); });
it('should not parse the dependency if the member expression is right side of an assignment expression', () => { it('should not parse the dependency if the member expression is right side of an assignment expression', () => {
const viewParticles = parse('Comp(flag = flag + 1)'); const viewParticles = parse('Comp(flag = flag + 1)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toHaveLength(0); expect(content?.depMask).toEqual(0);
}); });
it('should parse the dependency as identifiers', () => { it('should parse the dependency as identifiers', () => {
reactivityConfig.dependencyParseType = 'identifier'; reactivityConfig.dependencyParseType = 'identifier';
const viewParticles = parse('Comp(flag + count)'); const viewParticles = parse('Comp(flag + count)');
const content = (viewParticles[0] as CompParticle).props._$content; const content = (viewParticles[0] as CompParticle).props._$content;
expect(content?.dependencyIndexArr).toContain(availableProperties.indexOf('flag')); expect(content?.depMask).toEqual(0b11);
expect(content?.dependencyIndexArr).toContain(availableProperties.indexOf('count'));
reactivityConfig.dependencyParseType = 'property'; reactivityConfig.dependencyParseType = 'property';
}); });
}); });

View File

@ -16,12 +16,6 @@ describe('OtherParticle', () => {
expect(viewParticles[0].type).toBe('if'); expect(viewParticles[0].type).toBe('if');
}); });
it('should parse an IfUnit as an SwitchParticle', () => {
const viewParticles = parse('switch(this.flag) { }');
expect(viewParticles.length).toBe(1);
expect(viewParticles[0].type).toBe('switch');
});
it('should parse a ForUnit as a ForParticle', () => { it('should parse a ForUnit as a ForParticle', () => {
const viewParticles = parse('for(const item of this.items) { div() }'); const viewParticles = parse('for(const item of this.items) { div() }');
expect(viewParticles.length).toBe(1); expect(viewParticles.length).toBe(1);
@ -34,11 +28,9 @@ describe('OtherParticle', () => {
expect(viewParticles.length).toBe(1); expect(viewParticles.length).toBe(1);
expect(viewParticles[0].type).toBe('for'); expect(viewParticles[0].type).toBe('for');
const divParticle = (viewParticles[0] as ForParticle).children[0] as HTMLParticle; const divParticle = (viewParticles[0] as unknown as ForParticle).children[0] as unknown as HTMLParticle;
const divDependency = divParticle.props?.textContent?.dependencyIndexArr; const divDependency = divParticle.props?.textContent?.depMask;
expect(divDependency).toContain(0); expect(divDependency).toEqual(0b1011);
expect(divDependency).toContain(1);
expect(divDependency).toContain(3);
}); });
it("should correctly parse ForUnit's deconstruct item dependencies from array", () => { it("should correctly parse ForUnit's deconstruct item dependencies from array", () => {
@ -46,11 +38,9 @@ describe('OtherParticle', () => {
expect(viewParticles.length).toBe(1); expect(viewParticles.length).toBe(1);
expect(viewParticles[0].type).toBe('for'); expect(viewParticles[0].type).toBe('for');
const divParticle = (viewParticles[0] as ForParticle).children[0] as HTMLParticle; const divParticle = (viewParticles[0] as unknown as ForParticle).children[0] as unknown as HTMLParticle;
const divDependency = divParticle.props?.textContent?.dependencyIndexArr; const divDependency = divParticle.props?.textContent?.depMask;
expect(divDependency).toContain(0); expect(divDependency).toEqual(0b1011);
expect(divDependency).toContain(1);
expect(divDependency).toContain(3);
}); });
it('should parse a EnvUnit as a EnvParticle', () => { it('should parse a EnvUnit as a EnvParticle', () => {
@ -64,10 +54,4 @@ describe('OtherParticle', () => {
expect(viewParticles.length).toBe(1); expect(viewParticles.length).toBe(1);
expect(viewParticles[0].type).toBe('exp'); expect(viewParticles[0].type).toBe('exp');
}); });
it('should parse a TryUnit as a TryParticle', () => {
const viewParticles = parse('try { div() } catch(e) { div() }');
expect(viewParticles.length).toBe(1);
expect(viewParticles[0].type).toBe('try');
});
}); });

View File

@ -207,15 +207,18 @@ const htmlTags = [
const snippetNames = ['MySnippet', 'InnerButton']; const snippetNames = ['MySnippet', 'InnerButton'];
export const availableProperties = ['flag', 'count', 'doubleCount', 'array', 'state1', 'state2', 'state3', 'state4']; export const availableProperties = ['flag', 'count', 'doubleCount', 'array', 'state1', 'state2', 'state3', 'state4'];
const dependencyMap = { const depMaskMap = new Map(
count: ['flag'], Object.entries({
doubleCount: ['count', 'flag'], flag: 0b1,
array: ['count', 'flag'], count: 0b11,
state1: ['count', 'flag'], doubleCount: 0b111,
state2: ['count', 'flag', 'state1'], array: 0b1011,
state3: ['count', 'flag', 'state1', 'state2'], state1: 0b10011,
state4: ['count', 'flag', 'state1', 'state2', 'state3'], state2: 0b110011,
}; state3: 0b1110011,
state4: 0b11110011,
})
);
const viewConfig: ViewParserConfig = { const viewConfig: ViewParserConfig = {
babelApi, babelApi,
@ -226,7 +229,7 @@ const viewConfig: ViewParserConfig = {
export const reactivityConfig: ReactivityParserConfig = { export const reactivityConfig: ReactivityParserConfig = {
babelApi, babelApi,
availableProperties, availableProperties,
dependencyMap, depMaskMap,
}; };
export function parseCode(code: string) { export function parseCode(code: string) {
@ -241,10 +244,7 @@ export function parseView(code: string) {
return parseViewFromStatement(parseCode(code)); return parseViewFromStatement(parseCode(code));
} }
export function parseReactivity(statement: t.BlockStatement) {
return pR(parseViewFromStatement(statement), reactivityConfig)[0];
}
export function parse(code: string) { export function parse(code: string) {
// @ts-expect-error TODO: switch unit test to jsx-parser
return pR(parseView(code), reactivityConfig)[0]; return pR(parseView(code), reactivityConfig)[0];
} }

View File

@ -3,7 +3,6 @@ import type Babel from '@babel/core';
export interface DependencyValue<T> { export interface DependencyValue<T> {
value: T; value: T;
dynamic: boolean; // to removed
depMask: number; // -> bit depMask: number; // -> bit
dependenciesNode: t.ArrayExpression; dependenciesNode: t.ArrayExpression;
} }
@ -20,7 +19,6 @@ export interface TemplateProp {
key: string; key: string;
path: number[]; path: number[];
value: t.Expression; value: t.Expression;
dynamic: boolean;
depMask: number; depMask: number;
dependenciesNode: t.ArrayExpression; dependenciesNode: t.ArrayExpression;
} }
@ -71,25 +69,6 @@ export interface IfParticle {
branches: IfBranch[]; branches: IfBranch[];
} }
export interface SwitchBranch {
case: DependencyValue<t.Expression>;
children: ViewParticle[];
break: boolean;
}
export interface SwitchParticle {
type: 'switch';
discriminant: DependencyValue<t.Expression>;
branches: SwitchBranch[];
}
export interface TryParticle {
type: 'try';
children: ViewParticle[];
exception: t.Identifier | t.ArrayPattern | t.ObjectPattern | null;
catchChildren: ViewParticle[];
}
export interface EnvParticle { export interface EnvParticle {
type: 'env'; type: 'env';
props: Record<string, DependencyProp>; props: Record<string, DependencyProp>;
@ -102,13 +81,6 @@ export interface ExpParticle {
props: Record<string, DependencyProp>; props: Record<string, DependencyProp>;
} }
export interface SnippetParticle {
type: 'snippet';
tag: string;
props: Record<string, DependencyProp>;
children: ViewParticle[];
}
export type ViewParticle = export type ViewParticle =
| TemplateParticle | TemplateParticle
| TextParticle | TextParticle
@ -117,17 +89,14 @@ export type ViewParticle =
| ForParticle | ForParticle
| IfParticle | IfParticle
| EnvParticle | EnvParticle
| ExpParticle | ExpParticle;
| SwitchParticle
| SnippetParticle
| TryParticle;
export interface ReactivityParserConfig { export interface ReactivityParserConfig {
babelApi: typeof Babel; babelApi: typeof Babel;
availableProperties: string[]; availableProperties: string[];
availableIdentifiers?: string[]; availableIdentifiers?: string[];
reactiveBitMap: ReactiveBitMap; depMaskMap: DepMaskMap;
identifierDepMap?: Record<string, string[]>; identifierDepMap?: Record<string, Bitmap>;
dependencyParseType?: 'property' | 'identifier'; dependencyParseType?: 'property' | 'identifier';
parseTemplate?: boolean; parseTemplate?: boolean;
reactivityFuncNames?: string[]; reactivityFuncNames?: string[];
@ -135,4 +104,4 @@ export interface ReactivityParserConfig {
// TODO: unify with the types in babel-inula-next-core // TODO: unify with the types in babel-inula-next-core
type Bitmap = number; type Bitmap = number;
export type ReactiveBitMap = Map<string, Bitmap>; export type DepMaskMap = Map<string, Bitmap>;