@alloy-js/python 0.0.1
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/CHANGELOG.md +3 -0
- package/LICENSE +7 -0
- package/api-extractor.json +4 -0
- package/dist/src/builtins/python.d.ts +9 -0
- package/dist/src/builtins/python.d.ts.map +1 -0
- package/dist/src/builtins/python.js +17 -0
- package/dist/src/components/Atom.d.ts +19 -0
- package/dist/src/components/Atom.d.ts.map +1 -0
- package/dist/src/components/Atom.js +82 -0
- package/dist/src/components/CallSignature.d.ts +79 -0
- package/dist/src/components/CallSignature.d.ts.map +1 -0
- package/dist/src/components/CallSignature.js +201 -0
- package/dist/src/components/ClassDeclaration.d.ts +37 -0
- package/dist/src/components/ClassDeclaration.d.ts.map +1 -0
- package/dist/src/components/ClassDeclaration.js +83 -0
- package/dist/src/components/ClassInstantiation.d.ts +24 -0
- package/dist/src/components/ClassInstantiation.d.ts.map +1 -0
- package/dist/src/components/ClassInstantiation.js +35 -0
- package/dist/src/components/Declaration.d.ts +48 -0
- package/dist/src/components/Declaration.d.ts.map +1 -0
- package/dist/src/components/Declaration.js +37 -0
- package/dist/src/components/EnumDeclaration.d.ts +164 -0
- package/dist/src/components/EnumDeclaration.d.ts.map +1 -0
- package/dist/src/components/EnumDeclaration.js +278 -0
- package/dist/src/components/EnumMember.d.ts +46 -0
- package/dist/src/components/EnumMember.d.ts.map +1 -0
- package/dist/src/components/EnumMember.js +67 -0
- package/dist/src/components/FunctionCallExpression.d.ts +19 -0
- package/dist/src/components/FunctionCallExpression.d.ts.map +1 -0
- package/dist/src/components/FunctionCallExpression.js +40 -0
- package/dist/src/components/FunctionDeclaration.d.ts +47 -0
- package/dist/src/components/FunctionDeclaration.d.ts.map +1 -0
- package/dist/src/components/FunctionDeclaration.js +107 -0
- package/dist/src/components/ImportStatement.d.ts +39 -0
- package/dist/src/components/ImportStatement.d.ts.map +1 -0
- package/dist/src/components/ImportStatement.js +104 -0
- package/dist/src/components/MemberExpression.d.ts +97 -0
- package/dist/src/components/MemberExpression.d.ts.map +1 -0
- package/dist/src/components/MemberExpression.js +308 -0
- package/dist/src/components/NoNamePolicy.d.ts +23 -0
- package/dist/src/components/NoNamePolicy.d.ts.map +1 -0
- package/dist/src/components/NoNamePolicy.js +27 -0
- package/dist/src/components/PyDoc.d.ts +90 -0
- package/dist/src/components/PyDoc.d.ts.map +1 -0
- package/dist/src/components/PyDoc.js +280 -0
- package/dist/src/components/PythonBlock.d.ts +23 -0
- package/dist/src/components/PythonBlock.d.ts.map +1 -0
- package/dist/src/components/PythonBlock.js +31 -0
- package/dist/src/components/Reference.d.ts +13 -0
- package/dist/src/components/Reference.d.ts.map +1 -0
- package/dist/src/components/Reference.js +18 -0
- package/dist/src/components/SourceFile.d.ts +46 -0
- package/dist/src/components/SourceFile.d.ts.map +1 -0
- package/dist/src/components/SourceFile.js +75 -0
- package/dist/src/components/StatementList.d.ts +25 -0
- package/dist/src/components/StatementList.d.ts.map +1 -0
- package/dist/src/components/StatementList.js +29 -0
- package/dist/src/components/VariableDeclaration.d.ts +62 -0
- package/dist/src/components/VariableDeclaration.d.ts.map +1 -0
- package/dist/src/components/VariableDeclaration.js +131 -0
- package/dist/src/components/index.d.ts +19 -0
- package/dist/src/components/index.d.ts.map +1 -0
- package/dist/src/components/index.js +18 -0
- package/dist/src/create-module.d.ts +16 -0
- package/dist/src/create-module.d.ts.map +1 -0
- package/dist/src/create-module.js +64 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/name-policy.d.ts +5 -0
- package/dist/src/name-policy.d.ts.map +1 -0
- package/dist/src/name-policy.js +47 -0
- package/dist/src/parameter-descriptor.d.ts +31 -0
- package/dist/src/parameter-descriptor.d.ts.map +1 -0
- package/dist/src/parameter-descriptor.js +1 -0
- package/dist/src/symbol-creation.d.ts +4 -0
- package/dist/src/symbol-creation.d.ts.map +1 -0
- package/dist/src/symbol-creation.js +24 -0
- package/dist/src/symbols/custom-output-scope.d.ts +10 -0
- package/dist/src/symbols/custom-output-scope.d.ts.map +1 -0
- package/dist/src/symbols/custom-output-scope.js +25 -0
- package/dist/src/symbols/index.d.ts +7 -0
- package/dist/src/symbols/index.d.ts.map +1 -0
- package/dist/src/symbols/index.js +6 -0
- package/dist/src/symbols/python-member-scope.d.ts +7 -0
- package/dist/src/symbols/python-member-scope.d.ts.map +1 -0
- package/dist/src/symbols/python-member-scope.js +9 -0
- package/dist/src/symbols/python-module-scope.d.ts +25 -0
- package/dist/src/symbols/python-module-scope.d.ts.map +1 -0
- package/dist/src/symbols/python-module-scope.js +52 -0
- package/dist/src/symbols/python-output-symbol.d.ts +19 -0
- package/dist/src/symbols/python-output-symbol.d.ts.map +1 -0
- package/dist/src/symbols/python-output-symbol.js +22 -0
- package/dist/src/symbols/reference.d.ts +4 -0
- package/dist/src/symbols/reference.d.ts.map +1 -0
- package/dist/src/symbols/reference.js +60 -0
- package/dist/src/symbols/scopes.d.ts +5 -0
- package/dist/src/symbols/scopes.d.ts.map +1 -0
- package/dist/src/symbols/scopes.js +4 -0
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +12 -0
- package/dist/test/callsignatures.test.d.ts +2 -0
- package/dist/test/callsignatures.test.d.ts.map +1 -0
- package/dist/test/callsignatures.test.js +276 -0
- package/dist/test/classdeclarations.test.d.ts +2 -0
- package/dist/test/classdeclarations.test.d.ts.map +1 -0
- package/dist/test/classdeclarations.test.js +397 -0
- package/dist/test/classinstantiations.test.d.ts +2 -0
- package/dist/test/classinstantiations.test.d.ts.map +1 -0
- package/dist/test/classinstantiations.test.js +168 -0
- package/dist/test/enums.test.d.ts +2 -0
- package/dist/test/enums.test.d.ts.map +1 -0
- package/dist/test/enums.test.js +211 -0
- package/dist/test/externals.test.d.ts +2 -0
- package/dist/test/externals.test.d.ts.map +1 -0
- package/dist/test/externals.test.js +219 -0
- package/dist/test/functioncallexpressions.test.d.ts +2 -0
- package/dist/test/functioncallexpressions.test.d.ts.map +1 -0
- package/dist/test/functioncallexpressions.test.js +156 -0
- package/dist/test/functiondeclaration.test.d.ts +2 -0
- package/dist/test/functiondeclaration.test.d.ts.map +1 -0
- package/dist/test/functiondeclaration.test.js +363 -0
- package/dist/test/imports.test.d.ts +2 -0
- package/dist/test/imports.test.d.ts.map +1 -0
- package/dist/test/imports.test.js +262 -0
- package/dist/test/memberexpressions.test.d.ts +2 -0
- package/dist/test/memberexpressions.test.d.ts.map +1 -0
- package/dist/test/memberexpressions.test.js +879 -0
- package/dist/test/namepolicies.test.d.ts +2 -0
- package/dist/test/namepolicies.test.d.ts.map +1 -0
- package/dist/test/namepolicies.test.js +109 -0
- package/dist/test/pydocs.test.d.ts +2 -0
- package/dist/test/pydocs.test.d.ts.map +1 -0
- package/dist/test/pydocs.test.js +500 -0
- package/dist/test/references.test.d.ts +2 -0
- package/dist/test/references.test.d.ts.map +1 -0
- package/dist/test/references.test.js +49 -0
- package/dist/test/sourcefiles.test.d.ts +2 -0
- package/dist/test/sourcefiles.test.d.ts.map +1 -0
- package/dist/test/sourcefiles.test.js +198 -0
- package/dist/test/utils.d.ts +23 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +88 -0
- package/dist/test/values.test.d.ts +2 -0
- package/dist/test/values.test.d.ts.map +1 -0
- package/dist/test/values.test.js +78 -0
- package/dist/test/variables.test.d.ts +2 -0
- package/dist/test/variables.test.d.ts.map +1 -0
- package/dist/test/variables.test.js +173 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +39 -0
- package/src/builtins/python.ts +20 -0
- package/src/components/Atom.tsx +76 -0
- package/src/components/CallSignature.tsx +251 -0
- package/src/components/ClassDeclaration.tsx +98 -0
- package/src/components/ClassInstantiation.tsx +54 -0
- package/src/components/Declaration.tsx +91 -0
- package/src/components/EnumDeclaration.tsx +291 -0
- package/src/components/EnumMember.tsx +92 -0
- package/src/components/FunctionCallExpression.tsx +36 -0
- package/src/components/FunctionDeclaration.tsx +121 -0
- package/src/components/ImportStatement.tsx +134 -0
- package/src/components/MemberExpression.tsx +456 -0
- package/src/components/NoNamePolicy.tsx +31 -0
- package/src/components/PyDoc.tsx +331 -0
- package/src/components/PythonBlock.tsx +26 -0
- package/src/components/Reference.tsx +21 -0
- package/src/components/SourceFile.tsx +93 -0
- package/src/components/StatementList.tsx +28 -0
- package/src/components/VariableDeclaration.tsx +180 -0
- package/src/components/index.ts +18 -0
- package/src/create-module.ts +102 -0
- package/src/index.ts +7 -0
- package/src/name-policy.ts +101 -0
- package/src/parameter-descriptor.ts +36 -0
- package/src/symbol-creation.ts +36 -0
- package/src/symbols/custom-output-scope.ts +35 -0
- package/src/symbols/index.ts +6 -0
- package/src/symbols/python-member-scope.ts +12 -0
- package/src/symbols/python-module-scope.ts +89 -0
- package/src/symbols/python-output-symbol.ts +36 -0
- package/src/symbols/reference.ts +99 -0
- package/src/symbols/scopes.ts +9 -0
- package/src/utils.ts +27 -0
- package/temp/api.json +7207 -0
- package/test/callsignatures.test.tsx +256 -0
- package/test/classdeclarations.test.tsx +320 -0
- package/test/classinstantiations.test.tsx +159 -0
- package/test/enums.test.tsx +203 -0
- package/test/externals.test.tsx +190 -0
- package/test/functioncallexpressions.test.tsx +145 -0
- package/test/functiondeclaration.test.tsx +327 -0
- package/test/imports.test.tsx +214 -0
- package/test/memberexpressions.test.tsx +725 -0
- package/test/namepolicies.test.tsx +109 -0
- package/test/pydocs.test.tsx +528 -0
- package/test/references.test.tsx +36 -0
- package/test/sourcefiles.test.tsx +131 -0
- package/test/utils.tsx +131 -0
- package/test/values.test.tsx +61 -0
- package/test/variables.test.tsx +153 -0
- package/tsconfig.json +12 -0
- package/tsdoc-metadata.json +11 -0
- package/vitest.config.ts +10 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitSymbol,
|
|
3
|
+
Name,
|
|
4
|
+
OutputScope,
|
|
5
|
+
OutputSymbolFlags,
|
|
6
|
+
Scope,
|
|
7
|
+
Show,
|
|
8
|
+
useMemberScope,
|
|
9
|
+
useScope,
|
|
10
|
+
} from "@alloy-js/core";
|
|
11
|
+
import { createPythonSymbol } from "../symbol-creation.js";
|
|
12
|
+
import { getCallSignatureProps } from "../utils.js";
|
|
13
|
+
import { CallSignature, CallSignatureProps } from "./CallSignature.jsx";
|
|
14
|
+
import { BaseDeclarationProps, Declaration } from "./Declaration.js";
|
|
15
|
+
import { PythonBlock } from "./PythonBlock.jsx";
|
|
16
|
+
import { NoNamePolicy } from "./index.js";
|
|
17
|
+
|
|
18
|
+
export interface FunctionDeclarationProps
|
|
19
|
+
extends BaseDeclarationProps,
|
|
20
|
+
CallSignatureProps {
|
|
21
|
+
async?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A Python function declaration.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <FunctionDeclaration
|
|
30
|
+
* name="my_function"
|
|
31
|
+
* returnType="int"
|
|
32
|
+
* parameters=[{name: "a", type: "int"},{name: "b", type: "str"}]>
|
|
33
|
+
* return a + b
|
|
34
|
+
* </FunctionDeclaration>
|
|
35
|
+
* ```
|
|
36
|
+
* This will generate:
|
|
37
|
+
* ```python
|
|
38
|
+
* def my_function(a: int, b: str) -> int:
|
|
39
|
+
* return a + b
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function FunctionDeclaration(props: FunctionDeclarationProps) {
|
|
43
|
+
const asyncKwd = props.async ? "async " : "";
|
|
44
|
+
const callSignatureProps = getCallSignatureProps(props, {});
|
|
45
|
+
const memberScope = useMemberScope();
|
|
46
|
+
let scope: OutputScope | undefined = undefined;
|
|
47
|
+
if (memberScope !== undefined) {
|
|
48
|
+
scope = memberScope.instanceMembers!;
|
|
49
|
+
} else {
|
|
50
|
+
scope = useScope();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sym = createPythonSymbol(
|
|
54
|
+
props.name,
|
|
55
|
+
{
|
|
56
|
+
scope: scope,
|
|
57
|
+
refkeys: props.refkey,
|
|
58
|
+
flags: props.flags ?? OutputSymbolFlags.None,
|
|
59
|
+
},
|
|
60
|
+
"function",
|
|
61
|
+
false,
|
|
62
|
+
);
|
|
63
|
+
emitSymbol(sym);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<Declaration {...props} nameKind="function" symbol={sym}>
|
|
68
|
+
{asyncKwd}def <Name />
|
|
69
|
+
<Scope name={sym.name} kind="function">
|
|
70
|
+
<CallSignature
|
|
71
|
+
{...callSignatureProps}
|
|
72
|
+
returnType={props.returnType}
|
|
73
|
+
/>
|
|
74
|
+
<PythonBlock opener=":">
|
|
75
|
+
<Show when={Boolean(props.doc)}>{props.doc}</Show>
|
|
76
|
+
{props.children ?? "pass"}
|
|
77
|
+
</PythonBlock>
|
|
78
|
+
</Scope>
|
|
79
|
+
</Declaration>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A Python `__init__` function declaration.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* <InitFunctionDeclaration>
|
|
90
|
+
* self.attribute = "value"
|
|
91
|
+
* </InitFunctionDeclaration>
|
|
92
|
+
* ```
|
|
93
|
+
* This will generate:
|
|
94
|
+
* ```python
|
|
95
|
+
* def __init__(self: MyClass) -> None:
|
|
96
|
+
* self.attribute = "value"
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @remarks
|
|
100
|
+
*
|
|
101
|
+
* This is a convenience component that sets the name to `__init__`, marks it as
|
|
102
|
+
* an instance function, and forces the name to be `__init__` without applying
|
|
103
|
+
* the name policy.
|
|
104
|
+
*/
|
|
105
|
+
export function InitFunctionDeclaration(
|
|
106
|
+
props: Omit<
|
|
107
|
+
FunctionDeclarationProps,
|
|
108
|
+
"name" | "instanceFunction" | "classFunction"
|
|
109
|
+
>,
|
|
110
|
+
) {
|
|
111
|
+
return (
|
|
112
|
+
<NoNamePolicy>
|
|
113
|
+
<FunctionDeclaration
|
|
114
|
+
{...props}
|
|
115
|
+
name="__init__"
|
|
116
|
+
instanceFunction={true}
|
|
117
|
+
classFunction={false}
|
|
118
|
+
/>
|
|
119
|
+
</NoNamePolicy>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { computed, mapJoin, memo } from "@alloy-js/core";
|
|
2
|
+
import { ImportedSymbol, ImportRecords } from "../symbols/index.js";
|
|
3
|
+
|
|
4
|
+
export interface ImportStatementsProps {
|
|
5
|
+
records: ImportRecords;
|
|
6
|
+
joinImportsFromSameModule?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A component that renders import statements based on the provided import records.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* This component will render import statements for each module and its symbols.
|
|
14
|
+
* If `joinImportsFromSameModule` is true, it will group imports from the same module
|
|
15
|
+
* into a single statement.
|
|
16
|
+
*/
|
|
17
|
+
export function ImportStatements(props: ImportStatementsProps) {
|
|
18
|
+
// Sort the import records by module name
|
|
19
|
+
const imports = computed(() =>
|
|
20
|
+
[...props.records].sort(([a], [b]) => {
|
|
21
|
+
return a.name.localeCompare(b.name);
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return mapJoin(
|
|
26
|
+
() => imports.value,
|
|
27
|
+
([module, properties]) => {
|
|
28
|
+
// Only handling absolute imports for now
|
|
29
|
+
const targetPath = module.name;
|
|
30
|
+
|
|
31
|
+
if (properties.symbols && properties.symbols.size > 0) {
|
|
32
|
+
// Sort the symbols in a module by the imported name
|
|
33
|
+
const sortedSymbols = Array.from(properties.symbols).sort((a, b) =>
|
|
34
|
+
a.local.name.localeCompare(b.local.name),
|
|
35
|
+
);
|
|
36
|
+
if (props.joinImportsFromSameModule) {
|
|
37
|
+
// If joinImportsFromSameModule is true, we will group imports from the same module
|
|
38
|
+
return (
|
|
39
|
+
<ImportStatement
|
|
40
|
+
path={targetPath}
|
|
41
|
+
symbols={new Set(sortedSymbols)}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
return sortedSymbols.map((symbol, idx, arr) => (
|
|
46
|
+
<>
|
|
47
|
+
<ImportStatement path={targetPath} symbols={new Set([symbol])} />
|
|
48
|
+
{idx < arr.length - 1 && <hbr />}
|
|
49
|
+
</>
|
|
50
|
+
));
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// If no symbols are specified, it's either a wildcard import or a module import
|
|
54
|
+
return (
|
|
55
|
+
<ImportStatement path={targetPath} symbols={properties.symbols} />
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ImportStatementProps {
|
|
63
|
+
path: string;
|
|
64
|
+
symbols?: Set<ImportedSymbol>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A Python import statement.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* This component renders an import statement for a given path and symbols.
|
|
72
|
+
* If no symbols are provided, it will render a simple import statement.
|
|
73
|
+
* If symbols are provided, it will render an import statement with the specified symbols.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <ImportStatement path="os" />
|
|
78
|
+
* <ImportStatement path="math" symbols={new Set([new ImportedSymbol("sqrt", "sqrt")])} />
|
|
79
|
+
* ```
|
|
80
|
+
* This will generate:
|
|
81
|
+
* ```python
|
|
82
|
+
* import os
|
|
83
|
+
* from math import sqrt
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function ImportStatement(props: ImportStatementProps) {
|
|
87
|
+
return memo(() => {
|
|
88
|
+
const { path, symbols } = props;
|
|
89
|
+
const importSymbols: ImportedSymbol[] = [];
|
|
90
|
+
|
|
91
|
+
if (symbols && symbols.size > 0) {
|
|
92
|
+
for (const sym of symbols) {
|
|
93
|
+
importSymbols.push(sym);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parts: any[] = [];
|
|
98
|
+
|
|
99
|
+
if (!symbols || symbols.size === 0) {
|
|
100
|
+
parts.push(`import ${path}`);
|
|
101
|
+
} else {
|
|
102
|
+
importSymbols.sort((a, b) => {
|
|
103
|
+
return a.target.name.localeCompare(b.target.name);
|
|
104
|
+
});
|
|
105
|
+
parts.push(`from ${path} import `);
|
|
106
|
+
parts.push(
|
|
107
|
+
mapJoin(
|
|
108
|
+
() => importSymbols,
|
|
109
|
+
(nis) => <ImportBinding importedSymbol={nis} />,
|
|
110
|
+
{ joiner: ", " },
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return parts;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface ImportBindingProps {
|
|
119
|
+
importedSymbol: ImportedSymbol;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ImportBinding(props: Readonly<ImportBindingProps>) {
|
|
123
|
+
const text = memo(() => {
|
|
124
|
+
const localName = props.importedSymbol.local.name;
|
|
125
|
+
const targetName = props.importedSymbol.target.name;
|
|
126
|
+
if (localName === targetName) {
|
|
127
|
+
return targetName;
|
|
128
|
+
} else {
|
|
129
|
+
return `${targetName} as ${localName}`;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return <>{text()}</>;
|
|
134
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
childrenArray,
|
|
4
|
+
code,
|
|
5
|
+
computed,
|
|
6
|
+
For,
|
|
7
|
+
isComponentCreator,
|
|
8
|
+
OutputSymbol,
|
|
9
|
+
reactive,
|
|
10
|
+
ref,
|
|
11
|
+
Refkey,
|
|
12
|
+
Show,
|
|
13
|
+
takeSymbols,
|
|
14
|
+
ToRefs,
|
|
15
|
+
useBinder,
|
|
16
|
+
} from "@alloy-js/core";
|
|
17
|
+
|
|
18
|
+
export interface MemberExpressionProps {
|
|
19
|
+
children: Children;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const MEMBER_ACCESS_TYPES = {
|
|
23
|
+
ATTRIBUTE: "attribute",
|
|
24
|
+
SUBSCRIPTION: "subscription",
|
|
25
|
+
CALL: "call",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
interface AttributeDescriptor extends PartDescriptorBase {
|
|
29
|
+
type: typeof MEMBER_ACCESS_TYPES.ATTRIBUTE;
|
|
30
|
+
name: Children;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SubscriptionDescriptor extends PartDescriptorBase {
|
|
34
|
+
type: typeof MEMBER_ACCESS_TYPES.SUBSCRIPTION;
|
|
35
|
+
expression: Children;
|
|
36
|
+
quoted: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CallDescriptor extends PartDescriptorBase {
|
|
40
|
+
type: typeof MEMBER_ACCESS_TYPES.CALL;
|
|
41
|
+
args: Children[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface PartDescriptorBase {
|
|
45
|
+
type:
|
|
46
|
+
| typeof MEMBER_ACCESS_TYPES.CALL
|
|
47
|
+
| typeof MEMBER_ACCESS_TYPES.SUBSCRIPTION
|
|
48
|
+
| typeof MEMBER_ACCESS_TYPES.ATTRIBUTE;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type PartDescriptor =
|
|
52
|
+
| AttributeDescriptor
|
|
53
|
+
| SubscriptionDescriptor
|
|
54
|
+
| CallDescriptor;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a member expression from parts. Each part can provide one of
|
|
58
|
+
* the following:
|
|
59
|
+
*
|
|
60
|
+
* * **id**: The identifier for the member expression part
|
|
61
|
+
* * **refkey**: a refkey for a symbol whose name becomes the identifier
|
|
62
|
+
* * **symbol**: a symbol whose name becomes the identifier part
|
|
63
|
+
* * **args**: create a method call with the given args
|
|
64
|
+
* * **children**: arbitrary contents for the identifier part.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
*
|
|
68
|
+
* ```tsx
|
|
69
|
+
* <MemberExpression>
|
|
70
|
+
* <MemberExpression.Part id="base" />
|
|
71
|
+
* <MemberExpression.Part refkey={rk} />
|
|
72
|
+
* <MemberExpression.Part symbol={sym} />
|
|
73
|
+
* <MemberExpression.Part args={["hello", "world"]} />
|
|
74
|
+
* <MemberExpression.Part>SomeValue</MemberExpression.Part>
|
|
75
|
+
* </MemberExpression>
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Assuming `rk` is a refkey to a symbol name "prop1", and `sym` is a symbol
|
|
79
|
+
* with a name of "prop2", this will render:
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* base.prop1.prop2("hello", "world").SomeValue
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function MemberExpression(props: MemberExpressionProps): Children {
|
|
86
|
+
const children = childrenArray(() => props.children);
|
|
87
|
+
const parts = childrenToPartDescriptors(children);
|
|
88
|
+
// any symbols emitted from the children won't be relevant to
|
|
89
|
+
// parent scopes. TODO: emit the proper symbol if we know it?
|
|
90
|
+
takeSymbols();
|
|
91
|
+
|
|
92
|
+
if (parts.length === 0) {
|
|
93
|
+
return <></>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return computed(() => {
|
|
97
|
+
return formatChain(parts);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build part descriptors from the children of a MemberExpression.
|
|
103
|
+
*/
|
|
104
|
+
function childrenToPartDescriptors(children: Children[]): PartDescriptor[] {
|
|
105
|
+
const parts: PartDescriptor[] = [];
|
|
106
|
+
for (const child of children) {
|
|
107
|
+
if (!isComponentCreator(child, MemberExpression.Part)) {
|
|
108
|
+
// we ignore non-parts
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
parts.push(
|
|
113
|
+
createPartDescriptorFromProps(child.props, child === children[0]),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return parts;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const exclusiveParts: (keyof MemberExpressionPartProps)[] = [
|
|
121
|
+
"args",
|
|
122
|
+
"refkey",
|
|
123
|
+
"symbol",
|
|
124
|
+
"id",
|
|
125
|
+
"key",
|
|
126
|
+
"keys",
|
|
127
|
+
"slice",
|
|
128
|
+
];
|
|
129
|
+
/**
|
|
130
|
+
* Creates a reactive part descriptor from the given part props.
|
|
131
|
+
*
|
|
132
|
+
* @param partProps The props for the part.
|
|
133
|
+
* @param first Whether this is the first part in the expression. Refkeys are
|
|
134
|
+
* handled specially for the first part.
|
|
135
|
+
*/
|
|
136
|
+
function createPartDescriptorFromProps(
|
|
137
|
+
partProps: MemberExpressionPartProps,
|
|
138
|
+
first: boolean,
|
|
139
|
+
) {
|
|
140
|
+
const foundProps = exclusiveParts.filter((key) => {
|
|
141
|
+
return key in partProps;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (foundProps.length > 1) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Only one of ${foundProps.join(", ")} can be used for a MemberExpression part at a time`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Validate slice syntax
|
|
151
|
+
if (partProps.slice) {
|
|
152
|
+
const { start, stop, step } = partProps.slice;
|
|
153
|
+
if (!start && !stop && !step) {
|
|
154
|
+
throw new Error("MemberExpression.Part: slice object cannot be empty");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Validate keys array
|
|
159
|
+
if (partProps.keys?.length === 0) {
|
|
160
|
+
throw new Error("MemberExpression.Part: keys array cannot be empty");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const symbolSource = computed(() => {
|
|
164
|
+
if (partProps.refkey) {
|
|
165
|
+
return getSymbolForRefkey(partProps.refkey).value;
|
|
166
|
+
} else if (partProps.symbol) {
|
|
167
|
+
return partProps.symbol;
|
|
168
|
+
} else {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Return different descriptor types based on what props are provided
|
|
174
|
+
let part: ToRefs<PartDescriptor>;
|
|
175
|
+
if (partProps.args !== undefined) {
|
|
176
|
+
// CallDescriptor
|
|
177
|
+
part = {
|
|
178
|
+
type: computed(() => {
|
|
179
|
+
return "call" as const;
|
|
180
|
+
}),
|
|
181
|
+
args: ref<any>(partProps.args === true ? [] : partProps.args),
|
|
182
|
+
};
|
|
183
|
+
} else if (
|
|
184
|
+
partProps.key !== undefined ||
|
|
185
|
+
partProps.keys !== undefined ||
|
|
186
|
+
partProps.slice !== undefined
|
|
187
|
+
) {
|
|
188
|
+
// SubscriptionDescriptor
|
|
189
|
+
part = {
|
|
190
|
+
type: computed(() => {
|
|
191
|
+
return "subscription" as const;
|
|
192
|
+
}),
|
|
193
|
+
expression: computed(() => {
|
|
194
|
+
return getSubscriptionValue(partProps);
|
|
195
|
+
}),
|
|
196
|
+
quoted: computed(() => {
|
|
197
|
+
return partProps.key !== undefined && typeof partProps.key === "string";
|
|
198
|
+
}),
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
// IdentifierDescriptor
|
|
202
|
+
part = {
|
|
203
|
+
type: computed(() => {
|
|
204
|
+
return "attribute" as const;
|
|
205
|
+
}),
|
|
206
|
+
name: computed(() => {
|
|
207
|
+
if (first && partProps.refkey) {
|
|
208
|
+
return partProps.refkey;
|
|
209
|
+
} else if (partProps.id !== undefined) {
|
|
210
|
+
if (!isValidIdentifier(partProps.id)) {
|
|
211
|
+
throw new Error(`Invalid identifier: ${partProps.id}`);
|
|
212
|
+
}
|
|
213
|
+
return partProps.id;
|
|
214
|
+
} else if (symbolSource.value) {
|
|
215
|
+
return symbolSource.value.name;
|
|
216
|
+
} else {
|
|
217
|
+
return "<unresolved symbol>";
|
|
218
|
+
}
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return reactive(part);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Convert a refkey to a symbol ref using the current binder.
|
|
227
|
+
*/
|
|
228
|
+
function getSymbolForRefkey(refkey: Refkey) {
|
|
229
|
+
const binder = useBinder();
|
|
230
|
+
return binder!.getSymbolForRefkey(refkey);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Format a chain of parts into a MemberExpression.
|
|
235
|
+
*/
|
|
236
|
+
function formatChain(parts: PartDescriptor[]): Children {
|
|
237
|
+
return computed(() => {
|
|
238
|
+
const expression: Children[] = [];
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < parts.length; i++) {
|
|
241
|
+
const part = parts[i];
|
|
242
|
+
if (i === 0) {
|
|
243
|
+
if (!isAttributeDescriptor(part)) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"The first part of a MemberExpression must be an id or refkey",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
expression.push(part.name);
|
|
249
|
+
} else {
|
|
250
|
+
if (isCallDescriptor(part)) {
|
|
251
|
+
expression.push(formatCallOutput(part));
|
|
252
|
+
} else if (isAttributeDescriptor(part)) {
|
|
253
|
+
expression.push(formatAttributeOutput(part));
|
|
254
|
+
} else if (isSubscriptionDescriptor(part)) {
|
|
255
|
+
expression.push(formatSubscriptionOutput(part));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return expression;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Format a part of a member expression that is an array access.
|
|
266
|
+
* This is used for parts like `foo[0]` or `foo["bar"]`.
|
|
267
|
+
*/
|
|
268
|
+
function formatSubscriptionOutput(part: SubscriptionDescriptor) {
|
|
269
|
+
return (
|
|
270
|
+
<group>
|
|
271
|
+
{""}[
|
|
272
|
+
<indent>
|
|
273
|
+
<sbr />
|
|
274
|
+
{part.quoted && '"'}
|
|
275
|
+
{part.expression}
|
|
276
|
+
{part.quoted && '"'}
|
|
277
|
+
</indent>
|
|
278
|
+
<sbr />]
|
|
279
|
+
</group>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function formatAttributeOutput(part: AttributeDescriptor) {
|
|
284
|
+
return (
|
|
285
|
+
<group>
|
|
286
|
+
<indent>
|
|
287
|
+
<ifBreak> \</ifBreak>
|
|
288
|
+
<sbr />
|
|
289
|
+
{"."}
|
|
290
|
+
{part.name}
|
|
291
|
+
</indent>
|
|
292
|
+
</group>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function formatCallOutput(part: CallDescriptor) {
|
|
297
|
+
const args = computed(() => {
|
|
298
|
+
return typeof part.args === "boolean" ? [] : (part.args ?? []);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<group>
|
|
303
|
+
{""}(<Show when={args.value.length <= 1}>{args.value[0]}</Show>
|
|
304
|
+
<Show when={args.value.length > 1}>
|
|
305
|
+
<indent>
|
|
306
|
+
<sbr />
|
|
307
|
+
<For each={args} comma line>
|
|
308
|
+
{(arg) => arg}
|
|
309
|
+
</For>
|
|
310
|
+
</indent>
|
|
311
|
+
<sbr />
|
|
312
|
+
</Show>
|
|
313
|
+
)
|
|
314
|
+
</group>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface MemberExpressionPartProps {
|
|
319
|
+
/**
|
|
320
|
+
* The identifier for attribute access (obj.attr).
|
|
321
|
+
* Use this for Python attribute references.
|
|
322
|
+
*/
|
|
323
|
+
id?: string | number;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Single key for subscription access (obj[key] or obj[0]).
|
|
327
|
+
* Use this for single subscription access.
|
|
328
|
+
*/
|
|
329
|
+
key?: Children;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Multiple keys for tuple subscription access (obj[a, b] -\> obj[(a, b)]).
|
|
333
|
+
* Use this when you need tuple key access.
|
|
334
|
+
*/
|
|
335
|
+
keys?: Children[];
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Slice notation for subscription access (obj[start:stop:step]).
|
|
339
|
+
* Use this for Python slice syntax.
|
|
340
|
+
*/
|
|
341
|
+
slice?: {
|
|
342
|
+
start?: Children;
|
|
343
|
+
stop?: Children;
|
|
344
|
+
step?: Children;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* A refkey for a symbol whose name becomes the identifier.
|
|
349
|
+
*/
|
|
350
|
+
refkey?: Refkey;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* A symbol whose name becomes the identifier.
|
|
354
|
+
*/
|
|
355
|
+
symbol?: OutputSymbol;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Arguments to construct a call expression.
|
|
359
|
+
*/
|
|
360
|
+
args?: Children[] | boolean;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* A part of a member expression. Each part can provide one of the following
|
|
364
|
+
* props:
|
|
365
|
+
*
|
|
366
|
+
* * **id**: The identifier for the member expression part
|
|
367
|
+
* * **refkey**: A refkey for a symbol whose name becomes the identifier
|
|
368
|
+
* * **symbol**: a symbol whose name becomes the identifier part
|
|
369
|
+
* * **args**: create a method call with the given args
|
|
370
|
+
*/
|
|
371
|
+
MemberExpression.Part = function (props: MemberExpressionPartProps) {
|
|
372
|
+
/**
|
|
373
|
+
* This component does nothing except hold props which are retrieved by
|
|
374
|
+
* the `MemberExpression` component.
|
|
375
|
+
*/
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
function isValidIdentifier(id: Children) {
|
|
379
|
+
if (typeof id === "string" && id.includes('"')) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
function isCallDescriptor(part: PartDescriptor): part is CallDescriptor {
|
|
385
|
+
return "type" in part && part.type === MEMBER_ACCESS_TYPES.CALL;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function isAttributeDescriptor(
|
|
389
|
+
part: PartDescriptor,
|
|
390
|
+
): part is AttributeDescriptor {
|
|
391
|
+
return "type" in part && part.type === MEMBER_ACCESS_TYPES.ATTRIBUTE;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isSubscriptionDescriptor(
|
|
395
|
+
part: PartDescriptor,
|
|
396
|
+
): part is SubscriptionDescriptor {
|
|
397
|
+
return "type" in part && part.type === MEMBER_ACCESS_TYPES.SUBSCRIPTION;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function getNameForRefkey(refkey: Children): Children {
|
|
401
|
+
const parsedValue = getSymbolForRefkey(refkey as Refkey).value;
|
|
402
|
+
return parsedValue !== undefined ? parsedValue.name : refkey;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export interface SubscriptionProps {
|
|
406
|
+
/**
|
|
407
|
+
* Single key for subscription access (obj[key] or obj[0]).
|
|
408
|
+
* Use this for single subscription access.
|
|
409
|
+
*/
|
|
410
|
+
key?: Children;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Multiple keys for tuple subscription access (obj[a, b] -\> obj[(a, b)]).
|
|
414
|
+
* Use this when you need tuple key access.
|
|
415
|
+
*/
|
|
416
|
+
keys?: Children[];
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Slice notation for subscription access (obj[start:stop:step]).
|
|
420
|
+
* Use this for Python slice syntax.
|
|
421
|
+
*/
|
|
422
|
+
slice?: {
|
|
423
|
+
start?: Children;
|
|
424
|
+
stop?: Children;
|
|
425
|
+
step?: Children;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function getSubscriptionValue(partProps: SubscriptionProps): Children {
|
|
430
|
+
// Handle tuple keys: obj[a, b] → (a, b)
|
|
431
|
+
if (partProps.keys?.length) {
|
|
432
|
+
const parsedKeys = partProps.keys.map((key) =>
|
|
433
|
+
getNameForRefkey(key as Refkey),
|
|
434
|
+
);
|
|
435
|
+
return code`${parsedKeys.join(", ")}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Handle slice: obj[start:stop:step]
|
|
439
|
+
if (partProps.slice && Object.keys(partProps.slice).length > 0) {
|
|
440
|
+
const { start, stop, step } = partProps.slice;
|
|
441
|
+
const parts = [
|
|
442
|
+
start ? getNameForRefkey(start as Refkey) : "",
|
|
443
|
+
":",
|
|
444
|
+
stop ? getNameForRefkey(stop as Refkey) : "",
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
if (step) {
|
|
448
|
+
parts.push(":", getNameForRefkey(step as Refkey));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return code`${parts.join("")}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Handle single key: obj[key]
|
|
455
|
+
return getNameForRefkey(partProps.key as Refkey);
|
|
456
|
+
}
|