@lokascript/framework 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +142 -0
- package/dist/aot/aot-orchestrator.d.ts +75 -0
- package/dist/aot/aot-orchestrator.d.ts.map +1 -0
- package/dist/aot/domain-scanner.d.ts +27 -0
- package/dist/aot/domain-scanner.d.ts.map +1 -0
- package/dist/aot/index.d.ts +8 -0
- package/dist/aot/index.d.ts.map +1 -0
- package/dist/aot/types.d.ts +103 -0
- package/dist/aot/types.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts +91 -0
- package/dist/api/create-dsl.d.ts.map +1 -0
- package/dist/api/dispatcher.d.ts +108 -0
- package/dist/api/dispatcher.d.ts.map +1 -0
- package/dist/api/domain-registry.d.ts +152 -0
- package/dist/api/domain-registry.d.ts.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2082 -0
- package/dist/api/index.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2674 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +32 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.d.ts +6 -0
- package/dist/core/pattern-matching/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.js +1239 -0
- package/dist/core/pattern-matching/index.js.map +1 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts +239 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/index.d.ts +6 -0
- package/dist/core/pattern-matching/utils/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts +38 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts +63 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts.map +1 -0
- package/dist/core/tokenization/base-tokenizer.d.ts +344 -0
- package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -0
- package/dist/core/tokenization/char-classifiers.d.ts +56 -0
- package/dist/core/tokenization/char-classifiers.d.ts.map +1 -0
- package/dist/core/tokenization/default-extractors.d.ts +48 -0
- package/dist/core/tokenization/default-extractors.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/index.d.ts +9 -0
- package/dist/core/tokenization/extractors/index.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/operator.d.ts +23 -0
- package/dist/core/tokenization/extractors/operator.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts +22 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts.map +1 -0
- package/dist/core/tokenization/extractors.d.ts +61 -0
- package/dist/core/tokenization/extractors.d.ts.map +1 -0
- package/dist/core/tokenization/index.d.ts +11 -0
- package/dist/core/tokenization/index.d.ts.map +1 -0
- package/dist/core/tokenization/index.js +1345 -0
- package/dist/core/tokenization/index.js.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +5 -0
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/types.d.ts +110 -0
- package/dist/core/tokenization/morphology/types.d.ts.map +1 -0
- package/dist/core/tokenization/token-utils.d.ts +111 -0
- package/dist/core/tokenization/token-utils.d.ts.map +1 -0
- package/dist/core/types.d.ts +382 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +108 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generation/diagnostics.d.ts +120 -0
- package/dist/generation/diagnostics.d.ts.map +1 -0
- package/dist/generation/index.d.ts +7 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +339 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/generation/pattern-generator.d.ts +48 -0
- package/dist/generation/pattern-generator.d.ts.map +1 -0
- package/dist/generation/renderer.d.ts +115 -0
- package/dist/generation/renderer.d.ts.map +1 -0
- package/dist/grammar/index.d.ts +10 -0
- package/dist/grammar/index.d.ts.map +1 -0
- package/dist/grammar/index.js +391 -0
- package/dist/grammar/index.js.map +1 -0
- package/dist/grammar/transformer.d.ts +56 -0
- package/dist/grammar/transformer.d.ts.map +1 -0
- package/dist/grammar/types.d.ts +236 -0
- package/dist/grammar/types.d.ts.map +1 -0
- package/dist/index.cjs +4454 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4336 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/dictionary.d.ts +82 -0
- package/dist/interfaces/dictionary.d.ts.map +1 -0
- package/dist/interfaces/index.d.ts +10 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/profile-provider.d.ts +67 -0
- package/dist/interfaces/profile-provider.d.ts.map +1 -0
- package/dist/interfaces/value-extractor.d.ts +168 -0
- package/dist/interfaces/value-extractor.d.ts.map +1 -0
- package/dist/multilingual/index.d.ts +8 -0
- package/dist/multilingual/index.d.ts.map +1 -0
- package/dist/multilingual/index.js +1 -0
- package/dist/multilingual/index.js.map +1 -0
- package/dist/parsing/index.d.ts +8 -0
- package/dist/parsing/index.d.ts.map +1 -0
- package/dist/parsing/index.js +1415 -0
- package/dist/parsing/index.js.map +1 -0
- package/dist/parsing/multi-statement.d.ts +265 -0
- package/dist/parsing/multi-statement.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +78 -0
- package/dist/schema/command-schema.d.ts.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/test-setup.d.ts +9 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/testing/index.d.ts +50 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16969 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +122 -0
- package/src/__test__/fixtures/sql-dsl.ts +232 -0
- package/src/__test__/sql-integration.test.ts +189 -0
- package/src/__test__/test-utils.ts +260 -0
- package/src/aot/aot-orchestrator.test.ts +413 -0
- package/src/aot/aot-orchestrator.ts +238 -0
- package/src/aot/domain-scanner.ts +178 -0
- package/src/aot/index.ts +8 -0
- package/src/aot/types.ts +124 -0
- package/src/api/create-dsl.ts +367 -0
- package/src/api/dispatcher.test.ts +336 -0
- package/src/api/dispatcher.ts +222 -0
- package/src/api/domain-registry.test.ts +336 -0
- package/src/api/domain-registry.ts +500 -0
- package/src/api/index.ts +7 -0
- package/src/core/index.ts +7 -0
- package/src/core/logger.ts +130 -0
- package/src/core/pattern-matching/index.ts +6 -0
- package/src/core/pattern-matching/pattern-matcher.test.ts +900 -0
- package/src/core/pattern-matching/pattern-matcher.ts +1548 -0
- package/src/core/pattern-matching/pattern-matcher.ts.backup +1267 -0
- package/src/core/pattern-matching/utils/index.ts +6 -0
- package/src/core/pattern-matching/utils/possessive-keywords.ts +55 -0
- package/src/core/pattern-matching/utils/type-validation.test.ts +316 -0
- package/src/core/pattern-matching/utils/type-validation.ts +134 -0
- package/src/core/tokenization/base-tokenizer.ts +916 -0
- package/src/core/tokenization/char-classifiers.ts +79 -0
- package/src/core/tokenization/create-simple-tokenizer.test.ts +260 -0
- package/src/core/tokenization/default-extractors.ts +69 -0
- package/src/core/tokenization/extractors/index.ts +9 -0
- package/src/core/tokenization/extractors/operator.ts +75 -0
- package/src/core/tokenization/extractors/punctuation.ts +39 -0
- package/src/core/tokenization/extractors.ts +452 -0
- package/src/core/tokenization/index.ts +11 -0
- package/src/core/tokenization/morphology/index.ts +5 -0
- package/src/core/tokenization/morphology/types.ts +211 -0
- package/src/core/tokenization/token-utils.ts +252 -0
- package/src/core/types.ts +589 -0
- package/src/generation/diagnostics.test.ts +171 -0
- package/src/generation/diagnostics.ts +239 -0
- package/src/generation/index.ts +7 -0
- package/src/generation/pattern-generator.test.ts +430 -0
- package/src/generation/pattern-generator.ts +315 -0
- package/src/generation/renderer.test.ts +266 -0
- package/src/generation/renderer.ts +244 -0
- package/src/grammar/index.ts +12 -0
- package/src/grammar/transformer.ts +159 -0
- package/src/grammar/types.ts +630 -0
- package/src/index.ts +157 -0
- package/src/interfaces/dictionary.ts +123 -0
- package/src/interfaces/index.ts +10 -0
- package/src/interfaces/profile-provider.ts +88 -0
- package/src/interfaces/value-extractor.ts +435 -0
- package/src/multilingual/index.ts +9 -0
- package/src/parsing/index.ts +27 -0
- package/src/parsing/multi-statement.test.ts +480 -0
- package/src/parsing/multi-statement.ts +648 -0
- package/src/schema/command-schema.ts +118 -0
- package/src/schema/index.ts +5 -0
- package/src/test-setup.ts +45 -0
- package/src/testing/index.ts +137 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Possessive Keywords Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to look up possessive keywords from language profiles.
|
|
5
|
+
* Used by pattern-matcher to recognize possessive expressions like "my value".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal profile interface for possessive keyword lookup.
|
|
10
|
+
*/
|
|
11
|
+
export interface PossessiveProfile {
|
|
12
|
+
readonly possessive?: {
|
|
13
|
+
readonly keywords?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the reference for a possessive keyword from a profile.
|
|
19
|
+
*
|
|
20
|
+
* @param profile Language profile
|
|
21
|
+
* @param keyword Possessive keyword (e.g., 'my', 'your', 'its')
|
|
22
|
+
* @returns The reference (e.g., 'me', 'you', 'it') or undefined if not found
|
|
23
|
+
*/
|
|
24
|
+
export function getPossessiveReference(
|
|
25
|
+
profile: PossessiveProfile | undefined,
|
|
26
|
+
keyword: string
|
|
27
|
+
): string | undefined {
|
|
28
|
+
return profile?.possessive?.keywords?.[keyword];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a keyword is a possessive keyword in the given profile.
|
|
33
|
+
*
|
|
34
|
+
* @param profile Language profile
|
|
35
|
+
* @param keyword Keyword to check
|
|
36
|
+
* @returns True if the keyword is a possessive keyword
|
|
37
|
+
*/
|
|
38
|
+
export function isPossessiveKeyword(
|
|
39
|
+
profile: PossessiveProfile | undefined,
|
|
40
|
+
keyword: string
|
|
41
|
+
): boolean {
|
|
42
|
+
return profile?.possessive?.keywords?.[keyword] !== undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get all possessive keywords from a profile.
|
|
47
|
+
*
|
|
48
|
+
* @param profile Language profile
|
|
49
|
+
* @returns Record of possessive keywords to references, or empty object
|
|
50
|
+
*/
|
|
51
|
+
export function getAllPossessiveKeywords(
|
|
52
|
+
profile: PossessiveProfile | undefined
|
|
53
|
+
): Record<string, string> {
|
|
54
|
+
return profile?.possessive?.keywords ?? {};
|
|
55
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Validation Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the type compatibility and validation utilities used by PatternMatcher.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
isTypeCompatible,
|
|
10
|
+
validateValueType,
|
|
11
|
+
isCSSSelector,
|
|
12
|
+
isClassName,
|
|
13
|
+
isIdSelector,
|
|
14
|
+
isCSSPropertyRef,
|
|
15
|
+
isNumericValue,
|
|
16
|
+
isPropertyName,
|
|
17
|
+
isVariableRef,
|
|
18
|
+
isBuiltInReference,
|
|
19
|
+
} from './type-validation';
|
|
20
|
+
import type { SemanticValue } from '../../types';
|
|
21
|
+
|
|
22
|
+
describe('Type Validation', () => {
|
|
23
|
+
describe('isTypeCompatible', () => {
|
|
24
|
+
it('should return true for empty expected types array', () => {
|
|
25
|
+
expect(isTypeCompatible('literal', [])).toBe(true);
|
|
26
|
+
expect(isTypeCompatible('selector', [])).toBe(true);
|
|
27
|
+
expect(isTypeCompatible('unknown', [])).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return true for direct type match', () => {
|
|
31
|
+
expect(isTypeCompatible('literal', ['literal'])).toBe(true);
|
|
32
|
+
expect(isTypeCompatible('selector', ['selector'])).toBe(true);
|
|
33
|
+
expect(isTypeCompatible('reference', ['reference'])).toBe(true);
|
|
34
|
+
expect(isTypeCompatible('expression', ['expression'])).toBe(true);
|
|
35
|
+
expect(isTypeCompatible('property-path', ['property-path'])).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return true for type in expected types list', () => {
|
|
39
|
+
expect(isTypeCompatible('literal', ['literal', 'selector'])).toBe(true);
|
|
40
|
+
expect(isTypeCompatible('selector', ['literal', 'selector', 'reference'])).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return false for type not in expected types', () => {
|
|
44
|
+
expect(isTypeCompatible('literal', ['selector'])).toBe(false);
|
|
45
|
+
expect(isTypeCompatible('reference', ['literal', 'selector'])).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should treat expression as wildcard (matches everything)', () => {
|
|
49
|
+
expect(isTypeCompatible('literal', ['expression'])).toBe(true);
|
|
50
|
+
expect(isTypeCompatible('selector', ['expression'])).toBe(true);
|
|
51
|
+
expect(isTypeCompatible('reference', ['expression'])).toBe(true);
|
|
52
|
+
expect(isTypeCompatible('property-path', ['expression'])).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should allow property-path to match selector', () => {
|
|
56
|
+
expect(isTypeCompatible('property-path', ['selector'])).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should allow property-path to match reference', () => {
|
|
60
|
+
expect(isTypeCompatible('property-path', ['reference'])).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should allow property-path to match expression', () => {
|
|
64
|
+
expect(isTypeCompatible('property-path', ['expression'])).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should not allow property-path to match literal', () => {
|
|
68
|
+
expect(isTypeCompatible('property-path', ['literal'])).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle multiple expected types with property-path', () => {
|
|
72
|
+
expect(isTypeCompatible('property-path', ['literal', 'selector'])).toBe(true);
|
|
73
|
+
expect(isTypeCompatible('property-path', ['literal', 'reference'])).toBe(true);
|
|
74
|
+
expect(isTypeCompatible('property-path', ['literal', 'expression'])).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('validateValueType', () => {
|
|
79
|
+
it('should return true for undefined expected types', () => {
|
|
80
|
+
const value: SemanticValue = { type: 'literal', value: 'test' };
|
|
81
|
+
expect(validateValueType(value, undefined)).toBe(true);
|
|
82
|
+
expect(validateValueType(value, [])).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should validate literal values', () => {
|
|
86
|
+
const literal: SemanticValue = { type: 'literal', value: 'hello' };
|
|
87
|
+
expect(validateValueType(literal, ['literal'])).toBe(true);
|
|
88
|
+
expect(validateValueType(literal, ['selector'])).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should validate selector values', () => {
|
|
92
|
+
const selector: SemanticValue = { type: 'selector', value: '.button' };
|
|
93
|
+
expect(validateValueType(selector, ['selector'])).toBe(true);
|
|
94
|
+
expect(validateValueType(selector, ['literal'])).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should validate reference values', () => {
|
|
98
|
+
const reference: SemanticValue = { type: 'reference', value: 'me' };
|
|
99
|
+
expect(validateValueType(reference, ['reference'])).toBe(true);
|
|
100
|
+
expect(validateValueType(reference, ['literal'])).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should validate expression values', () => {
|
|
104
|
+
const expression: SemanticValue = { type: 'expression', raw: 'x + y' };
|
|
105
|
+
expect(validateValueType(expression, ['expression'])).toBe(true);
|
|
106
|
+
expect(validateValueType(expression, ['literal'])).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should validate property-path values', () => {
|
|
110
|
+
const propertyPath: SemanticValue = {
|
|
111
|
+
type: 'property-path',
|
|
112
|
+
object: { type: 'reference', value: 'me' },
|
|
113
|
+
property: 'value',
|
|
114
|
+
};
|
|
115
|
+
expect(validateValueType(propertyPath, ['property-path'])).toBe(true);
|
|
116
|
+
expect(validateValueType(propertyPath, ['selector'])).toBe(true);
|
|
117
|
+
expect(validateValueType(propertyPath, ['reference'])).toBe(true);
|
|
118
|
+
expect(validateValueType(propertyPath, ['expression'])).toBe(true);
|
|
119
|
+
expect(validateValueType(propertyPath, ['literal'])).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should allow any type when expression is expected', () => {
|
|
123
|
+
const literal: SemanticValue = { type: 'literal', value: 'test' };
|
|
124
|
+
const selector: SemanticValue = { type: 'selector', value: '.btn' };
|
|
125
|
+
const reference: SemanticValue = { type: 'reference', value: 'me' };
|
|
126
|
+
|
|
127
|
+
expect(validateValueType(literal, ['expression'])).toBe(true);
|
|
128
|
+
expect(validateValueType(selector, ['expression'])).toBe(true);
|
|
129
|
+
expect(validateValueType(reference, ['expression'])).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('CSS Selector Detection', () => {
|
|
134
|
+
describe('isCSSSelector', () => {
|
|
135
|
+
it('should detect class selectors', () => {
|
|
136
|
+
expect(isCSSSelector('.button')).toBe(true);
|
|
137
|
+
expect(isCSSSelector('.my-class')).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should detect ID selectors', () => {
|
|
141
|
+
expect(isCSSSelector('#header')).toBe(true);
|
|
142
|
+
expect(isCSSSelector('#my-id')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should detect element selectors', () => {
|
|
146
|
+
expect(isCSSSelector('<button/>')).toBe(true);
|
|
147
|
+
expect(isCSSSelector('<div/>')).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should not detect non-selectors', () => {
|
|
151
|
+
expect(isCSSSelector('button')).toBe(false);
|
|
152
|
+
expect(isCSSSelector('myVariable')).toBe(false);
|
|
153
|
+
expect(isCSSSelector('123')).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('isClassName', () => {
|
|
158
|
+
it('should detect class names', () => {
|
|
159
|
+
expect(isClassName('.active')).toBe(true);
|
|
160
|
+
expect(isClassName('.my-class')).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should not detect non-class selectors', () => {
|
|
164
|
+
expect(isClassName('#id')).toBe(false);
|
|
165
|
+
expect(isClassName('<div/>')).toBe(false);
|
|
166
|
+
expect(isClassName('button')).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('isIdSelector', () => {
|
|
171
|
+
it('should detect ID selectors', () => {
|
|
172
|
+
expect(isIdSelector('#header')).toBe(true);
|
|
173
|
+
expect(isIdSelector('#my-id')).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should not detect non-ID selectors', () => {
|
|
177
|
+
expect(isIdSelector('.class')).toBe(false);
|
|
178
|
+
expect(isIdSelector('<div/>')).toBe(false);
|
|
179
|
+
expect(isIdSelector('button')).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('isCSSPropertyRef', () => {
|
|
184
|
+
it('should detect CSS property references', () => {
|
|
185
|
+
expect(isCSSPropertyRef('*display')).toBe(true);
|
|
186
|
+
expect(isCSSPropertyRef('*width')).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should not detect non-property references', () => {
|
|
190
|
+
expect(isCSSPropertyRef('.class')).toBe(false);
|
|
191
|
+
expect(isCSSPropertyRef('#id')).toBe(false);
|
|
192
|
+
expect(isCSSPropertyRef('display')).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('Value Type Detection', () => {
|
|
198
|
+
describe('isNumericValue', () => {
|
|
199
|
+
it('should detect integer values', () => {
|
|
200
|
+
expect(isNumericValue('0')).toBe(true);
|
|
201
|
+
expect(isNumericValue('42')).toBe(true);
|
|
202
|
+
expect(isNumericValue('999')).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should detect decimal values', () => {
|
|
206
|
+
expect(isNumericValue('3.14')).toBe(true);
|
|
207
|
+
expect(isNumericValue('0.5')).toBe(true);
|
|
208
|
+
expect(isNumericValue('99.99')).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should detect duration values', () => {
|
|
212
|
+
expect(isNumericValue('100ms')).toBe(true);
|
|
213
|
+
expect(isNumericValue('2s')).toBe(true);
|
|
214
|
+
expect(isNumericValue('5m')).toBe(true);
|
|
215
|
+
expect(isNumericValue('1h')).toBe(true);
|
|
216
|
+
expect(isNumericValue('2.5s')).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should not detect non-numeric values', () => {
|
|
220
|
+
expect(isNumericValue('abc')).toBe(false);
|
|
221
|
+
expect(isNumericValue('test')).toBe(false);
|
|
222
|
+
expect(isNumericValue('')).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should not detect invalid numbers', () => {
|
|
226
|
+
expect(isNumericValue('NaN')).toBe(false);
|
|
227
|
+
expect(isNumericValue('Infinity')).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('isPropertyName', () => {
|
|
232
|
+
it('should detect valid property names', () => {
|
|
233
|
+
expect(isPropertyName('value')).toBe(true);
|
|
234
|
+
expect(isPropertyName('innerHTML')).toBe(true);
|
|
235
|
+
expect(isPropertyName('_private')).toBe(true);
|
|
236
|
+
expect(isPropertyName('camelCase')).toBe(true);
|
|
237
|
+
expect(isPropertyName('snake_case')).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should not detect invalid property names', () => {
|
|
241
|
+
expect(isPropertyName('123')).toBe(false); // starts with digit
|
|
242
|
+
expect(isPropertyName('my-prop')).toBe(false); // contains hyphen
|
|
243
|
+
expect(isPropertyName('.class')).toBe(false); // starts with dot
|
|
244
|
+
expect(isPropertyName('#id')).toBe(false); // starts with hash
|
|
245
|
+
expect(isPropertyName('has space')).toBe(false); // contains space
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('isVariableRef', () => {
|
|
250
|
+
it('should detect variable references', () => {
|
|
251
|
+
expect(isVariableRef(':myVar')).toBe(true);
|
|
252
|
+
expect(isVariableRef(':count')).toBe(true);
|
|
253
|
+
expect(isVariableRef(':_temp')).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should not detect non-variable values', () => {
|
|
257
|
+
expect(isVariableRef('myVar')).toBe(false);
|
|
258
|
+
expect(isVariableRef('.class')).toBe(false);
|
|
259
|
+
expect(isVariableRef('#id')).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('isBuiltInReference', () => {
|
|
264
|
+
it('should detect built-in references', () => {
|
|
265
|
+
expect(isBuiltInReference('me')).toBe(true);
|
|
266
|
+
expect(isBuiltInReference('you')).toBe(true);
|
|
267
|
+
expect(isBuiltInReference('it')).toBe(true);
|
|
268
|
+
expect(isBuiltInReference('result')).toBe(true);
|
|
269
|
+
expect(isBuiltInReference('event')).toBe(true);
|
|
270
|
+
expect(isBuiltInReference('target')).toBe(true);
|
|
271
|
+
expect(isBuiltInReference('body')).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should be case-insensitive', () => {
|
|
275
|
+
expect(isBuiltInReference('ME')).toBe(true);
|
|
276
|
+
expect(isBuiltInReference('You')).toBe(true);
|
|
277
|
+
expect(isBuiltInReference('IT')).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should not detect non-built-in references', () => {
|
|
281
|
+
expect(isBuiltInReference('myVar')).toBe(false);
|
|
282
|
+
expect(isBuiltInReference('custom')).toBe(false);
|
|
283
|
+
expect(isBuiltInReference('element')).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('Edge Cases', () => {
|
|
289
|
+
it('should handle null/undefined gracefully in isTypeCompatible', () => {
|
|
290
|
+
expect(isTypeCompatible('literal', null as any)).toBe(true);
|
|
291
|
+
expect(isTypeCompatible('literal', undefined as any)).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should handle empty strings', () => {
|
|
295
|
+
expect(isCSSSelector('')).toBe(false);
|
|
296
|
+
expect(isClassName('')).toBe(false);
|
|
297
|
+
expect(isIdSelector('')).toBe(false);
|
|
298
|
+
expect(isPropertyName('')).toBe(false);
|
|
299
|
+
expect(isNumericValue('')).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should handle whitespace', () => {
|
|
303
|
+
expect(isPropertyName(' value')).toBe(false);
|
|
304
|
+
expect(isPropertyName('value ')).toBe(false);
|
|
305
|
+
// Note: parseFloat handles leading/trailing whitespace
|
|
306
|
+
expect(isNumericValue(' 42')).toBe(true);
|
|
307
|
+
expect(isNumericValue('42 ')).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should handle special characters in property names', () => {
|
|
311
|
+
expect(isPropertyName('prop$')).toBe(false);
|
|
312
|
+
expect(isPropertyName('prop@')).toBe(false);
|
|
313
|
+
expect(isPropertyName('prop-name')).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Validation Utility
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for validating semantic value types against expected types.
|
|
5
|
+
* Used by pattern-matcher to validate captured role values.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SemanticValue } from '../../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Semantic value type strings.
|
|
12
|
+
*/
|
|
13
|
+
export type SemanticValueType = SemanticValue['type'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a value type matches any of the expected types.
|
|
17
|
+
*
|
|
18
|
+
* The 'expression' type is treated as a wildcard that matches any value.
|
|
19
|
+
* The 'property-path' type is compatible with selector, reference, and expression.
|
|
20
|
+
*
|
|
21
|
+
* @param actualType The actual type of the value
|
|
22
|
+
* @param expectedTypes Array of expected types
|
|
23
|
+
* @returns True if the type is compatible
|
|
24
|
+
*/
|
|
25
|
+
export function isTypeCompatible(
|
|
26
|
+
actualType: SemanticValueType | string,
|
|
27
|
+
expectedTypes: (SemanticValueType | string)[]
|
|
28
|
+
): boolean {
|
|
29
|
+
// Empty expected types means any type is valid
|
|
30
|
+
if (!expectedTypes || expectedTypes.length === 0) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Direct match
|
|
35
|
+
if (expectedTypes.includes(actualType)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 'expression' is always compatible (wildcard)
|
|
40
|
+
if (expectedTypes.includes('expression')) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 'property-path' is compatible with selector, reference, and expression
|
|
45
|
+
if (actualType === 'property-path') {
|
|
46
|
+
return expectedTypes.some(t => ['selector', 'reference', 'expression'].includes(t));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate a semantic value against expected types.
|
|
54
|
+
*
|
|
55
|
+
* @param value The semantic value to validate
|
|
56
|
+
* @param expectedTypes Array of expected types (empty means any type is valid)
|
|
57
|
+
* @returns True if the value type is valid
|
|
58
|
+
*/
|
|
59
|
+
export function validateValueType(
|
|
60
|
+
value: SemanticValue,
|
|
61
|
+
expectedTypes?: (SemanticValueType | string)[]
|
|
62
|
+
): boolean {
|
|
63
|
+
if (!expectedTypes || expectedTypes.length === 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return isTypeCompatible(value.type, expectedTypes);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if a value is a CSS selector.
|
|
72
|
+
*/
|
|
73
|
+
export function isCSSSelector(value: string): boolean {
|
|
74
|
+
return value.startsWith('.') || value.startsWith('#') || value.startsWith('<');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if a value is a class name (starts with .).
|
|
79
|
+
*/
|
|
80
|
+
export function isClassName(value: string): boolean {
|
|
81
|
+
return value.startsWith('.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if a value is an ID selector (starts with #).
|
|
86
|
+
*/
|
|
87
|
+
export function isIdSelector(value: string): boolean {
|
|
88
|
+
return value.startsWith('#');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a value is a CSS property reference (starts with *).
|
|
93
|
+
*/
|
|
94
|
+
export function isCSSPropertyRef(value: string): boolean {
|
|
95
|
+
return value.startsWith('*');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if a value is a numeric value.
|
|
100
|
+
*/
|
|
101
|
+
export function isNumericValue(value: string): boolean {
|
|
102
|
+
// Handle duration values (e.g., "100ms", "2s")
|
|
103
|
+
const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
104
|
+
if (durationMatch) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Plain number
|
|
109
|
+
const num = parseFloat(value);
|
|
110
|
+
return !isNaN(num) && isFinite(num);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if a value is a property name (identifier-like).
|
|
115
|
+
*/
|
|
116
|
+
export function isPropertyName(value: string): boolean {
|
|
117
|
+
// Property names are identifiers (start with letter or _, contain alphanumeric)
|
|
118
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a value is a variable reference (starts with :).
|
|
123
|
+
*/
|
|
124
|
+
export function isVariableRef(value: string): boolean {
|
|
125
|
+
return value.startsWith(':');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a value is a built-in reference (me, you, it, etc.).
|
|
130
|
+
*/
|
|
131
|
+
export function isBuiltInReference(value: string): boolean {
|
|
132
|
+
const builtIns = new Set(['me', 'you', 'it', 'result', 'event', 'target', 'body']);
|
|
133
|
+
return builtIns.has(value.toLowerCase());
|
|
134
|
+
}
|