diff --git a/jy/bridge.py b/jy/bridge.py index 7a006cf..2474fb4 100644 --- a/jy/bridge.py +++ b/jy/bridge.py @@ -1,6 +1,6 @@ """Python portals to JS""" -from typing import Any, Optional +from typing import Any from functools import partial from inspect import Parameter from keyword import iskeyword @@ -106,8 +106,8 @@ def add_js_funcs( js_code: str, *, obj: Any = None, - name: Optional[str] = None, - encoding: Optional[str] = None, + name: str | None = None, + encoding: str | None = None, forbidden_method_names=(), apply_defaults=True, value_trans=dflt_py_to_js_value_trans, diff --git a/jy/js_parse.py b/jy/js_parse.py index ae73bb6..cf9983e 100644 --- a/jy/js_parse.py +++ b/jy/js_parse.py @@ -4,7 +4,6 @@ from pathlib import Path from functools import wraps from itertools import chain -from typing import Optional import json import esprima @@ -42,7 +41,7 @@ def _func(*args, **kwargs): return _if_none_output_raise_wrapper -# def parse_js(js_code: str, encoding: Optional[str] = None) -> AstScript: +# def parse_js(js_code: str, encoding: str | None = None) -> AstScript: # if os.path.isfile(js_code): # js_code = Path(js_code).read_text(encoding=encoding) # return esprima.parse(js_code) @@ -50,7 +49,7 @@ def _func(*args, **kwargs): def parse_js( js_code: str, - encoding: Optional[str] = None, + encoding: str | None = None, *, ingress=lambda x: x, egress=lambda x: x, @@ -253,7 +252,6 @@ def dflt_py_to_js_value_trans(x): import re import uuid -from typing import Tuple _dflt_patterns_for_html_ids = ( r'id="([^"]+)"', # HTML id attributes @@ -263,8 +261,8 @@ def dflt_py_to_js_value_trans(x): def replace_ids_in_code( - code: str, patterns: Tuple[str] = _dflt_patterns_for_html_ids -) -> (str, dict): + code: str, patterns: tuple[str] = _dflt_patterns_for_html_ids +) -> tuple[str, dict]: """ Replaces IDs in a mixed HTML, CSS, and JS string with unique versions. diff --git a/jy/scrap/find_and_replace_ids.py b/jy/scrap/find_and_replace_ids.py index 349f125..d231cb1 100644 --- a/jy/scrap/find_and_replace_ids.py +++ b/jy/scrap/find_and_replace_ids.py @@ -13,7 +13,7 @@ import re from functools import partial -from typing import Callable, Optional, Union +from collections.abc import Callable def replace_tokens(string: str, token_extractor: Callable, token_replacer: Callable): @@ -158,10 +158,10 @@ def _string_kind(string, string_kind_rules=_dflt_string_kind_rules): def replace_ids( string, - kind: Optional[str] = None, + kind: str | None = None, *, replacer_for_kind=_dflt_replacer_for_kind, - string_to_kind: Optional[Callable] = _string_kind + string_to_kind: Callable | None = _string_kind ): kind = kind or string_to_kind(string) replacer_for_kind = dict(replacer_for_kind) diff --git a/jy/ts_parse.py b/jy/ts_parse.py index 75a32b9..3c278db 100644 --- a/jy/ts_parse.py +++ b/jy/ts_parse.py @@ -97,16 +97,17 @@ def parse_ts_with_oa( # ------------------------------------------------------------------------------------- # The grammar-based-parsing way -from typing import Iterator, Dict, Any, Optional, Tuple +from typing import Any +from collections.abc import Iterator -def extract_parameters(code: str, parameters_node) -> Iterator[Dict[str, Any]]: +def extract_parameters(code: str, parameters_node) -> Iterator[dict[str, Any]]: """ Generator function that yields parameter dictionaries. """ if parameters_node: for param_node in parameters_node.named_children: - param: Dict[str, Any] = {} + param: dict[str, Any] = {} # Get parameter name param_name_node = param_node.child_by_field_name("name") if param_name_node: @@ -130,7 +131,7 @@ def extract_parameters(code: str, parameters_node) -> Iterator[Dict[str, Any]]: yield param # Yield the parameter -def find_function_type_node(node) -> Optional[Any]: +def find_function_type_node(node) -> Any | None: """ Recursively search for a 'function_type' node within a given node. """ @@ -143,7 +144,7 @@ def find_function_type_node(node) -> Optional[Any]: return None -def handle_property_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, Any]]]: +def handle_property_signature(code: str, node) -> Iterator[tuple[str, dict[str, Any]]]: """ Handle 'property_signature' nodes, yielding (property name, property info) tuples. """ @@ -152,7 +153,7 @@ def handle_property_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, if name_node: prop_name = code[name_node.start_byte : name_node.end_byte] - prop_info: Dict[str, Any] = {"name": prop_name} + prop_info: dict[str, Any] = {"name": prop_name} # Check if the property is optional (has a '?') question_node = node.child_by_field_name("question_mark") @@ -178,7 +179,7 @@ def handle_property_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, yield (prop_name, prop_info) -def handle_method_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, Any]]]: +def handle_method_signature(code: str, node) -> Iterator[tuple[str, dict[str, Any]]]: """ Handle 'method_signature' nodes, yielding (method name, method info) tuples. """ @@ -188,7 +189,7 @@ def handle_method_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, An if name_node: method_name = code[name_node.start_byte : name_node.end_byte] - method_info: Dict[str, Any] = {"name": method_name} + method_info: dict[str, Any] = {"name": method_name} # Check if the method is optional (has a '?') question_node = node.child_by_field_name("question_mark") @@ -212,7 +213,7 @@ def handle_method_signature(code: str, node) -> Iterator[Tuple[str, Dict[str, An def handle_interface_declaration( code: str, node -) -> Iterator[Tuple[str, Dict[str, Any]]]: +) -> Iterator[tuple[str, dict[str, Any]]]: """ Handle 'interface_declaration' nodes by traversing their members. """ @@ -222,7 +223,7 @@ def handle_interface_declaration( yield from parse_ts(code, member_node) -def default_handler(code: str, node) -> Iterator[Tuple[str, Dict[str, Any]]]: +def default_handler(code: str, node) -> Iterator[tuple[str, dict[str, Any]]]: """ Default handler for nodes that don't have a specific handler. """ @@ -232,7 +233,7 @@ def default_handler(code: str, node) -> Iterator[Tuple[str, Dict[str, Any]]]: # Mapping from node types to handler functions -node_handlers: Dict[str, Any] = { +node_handlers: dict[str, Any] = { "property_signature": handle_property_signature, "method_signature": handle_method_signature, "interface_declaration": handle_interface_declaration, @@ -241,7 +242,7 @@ def default_handler(code: str, node) -> Iterator[Tuple[str, Dict[str, Any]]]: # TODO: Add ability to parse patterns that the simple_ts_parser handles here too. -def parse_ts(code: str, node=None) -> Iterator[Tuple[str, Dict[str, Any]]]: +def parse_ts(code: str, node=None) -> Iterator[tuple[str, dict[str, Any]]]: """ Generator function that traverses the syntax tree and yields (name, info dict) tuples. diff --git a/misc/js_and_ts_parsing_and_type_inference.ipynb b/misc/js_and_ts_parsing_and_type_inference.ipynb new file mode 100644 index 0000000..b99dedc --- /dev/null +++ b/misc/js_and_ts_parsing_and_type_inference.ipynb @@ -0,0 +1,1057 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3d63fec5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "bcf5c596", + "metadata": {}, + "source": [ + "## Summary: JavaScript Type Inference Tools\n", + "\n", + "Based on our exploration, here are the available tools and approaches for extracting type information from JavaScript:\n", + "\n", + "### 1. **Existing jy Package Capabilities**\n", + "- ✅ **Function name extraction**: `func_name_and_params_pairs()`\n", + "- ✅ **Parameter names**: Complete parameter identification\n", + "- ✅ **Default values**: Extracts default values from JavaScript\n", + "- ✅ **Basic type inference**: Can infer types from default values\n", + "\n", + "### 2. **Type Inference from Default Values**\n", + "- ✅ `string = \"hello\"` → `string`\n", + "- ✅ `number = 42` → `number` \n", + "- ✅ `boolean = true` → `boolean`\n", + "- ✅ `array = []` → `array`\n", + "- ✅ `object = {}` → `object`\n", + "\n", + "### 3. **Type Inference from Parameter Names**\n", + "- ✅ Heuristic patterns (e.g., `isActive` → `boolean`, `count` → `number`)\n", + "- ✅ Configurable pattern matching\n", + "- ✅ Confidence scoring\n", + "\n", + "### 4. **Advanced Features You Can Build**\n", + "- ✅ **Enhanced AST analysis** for complex default values\n", + "- ✅ **TypeScript interface generation** from JS functions\n", + "- ✅ **Confidence scoring** for type predictions\n", + "- ✅ **Multiple inference strategies** combined\n", + "\n", + "### 5. **TypeScript Parsing**\n", + "- ⚠️ **Partial support**: `jy` has TypeScript parsing (`parse_ts`) but needs setup\n", + "- ⚠️ **Dependencies**: Requires `tree-sitter` and `tree-sitter-languages`\n", + "- ✅ **Explicit types**: When working, can extract explicit TypeScript type annotations\n", + "\n", + "### 6. **Return Type Inference (Potential)**\n", + "- 🔄 **Function body analysis**: Analyze return statements\n", + "- 🔄 **Name-based inference**: Function naming patterns\n", + "- 🔄 **JSDoc parsing**: Extract `@returns` annotations\n", + "- 🔄 **TypeScript annotations**: Parse explicit return types\n", + "\n", + "### 7. **Recommendations**\n", + "\n", + "**For immediate use:**\n", + "```python\n", + "from jy import func_name_and_params_pairs\n", + "\n", + "# Use enhanced inference system (see definition of JavaScriptTypeInference in notebook below)\n", + "type_inferrer = JavaScriptTypeInference()\n", + "analysis = type_inferrer.analyze_functions(js_code)\n", + "```\n", + "\n", + "**For advanced type extraction:**\n", + "1. Use the `JavaScriptTypeInference` class we built\n", + "2. Combine default value analysis with name-based heuristics\n", + "3. Generate TypeScript interfaces for documentation\n", + "4. Add confidence scoring for type predictions\n", + "\n", + "**For TypeScript support:**\n", + "```bash\n", + "pip install tree-sitter==0.21.3 tree-sitter-languages\n", + "```\n", + "\n", + "The `jy` package provides an excellent foundation, and we've shown how to extend it with sophisticated type inference capabilities!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f595a85a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1cdc52f7", + "metadata": {}, + "source": [ + "# JavaScript Type Inference and Enhanced Parsing\n", + "\n", + "Let me explore the type information capabilities in JavaScript parsing, building on what we've learned about the `jy` package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acb9f2e4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "JavaScript code with various type scenarios:\n", + "\n", + "// Function with typed parameters and default values - we can infer types from defaults\n", + "function processData(name = \"defaultName\", age = 25, isActive = true, score = 3.14) {\n", + " return name + \" is \" + age + \" years old\";\n", + "}\n", + "\n", + "// Arrow function with defaults of different types\n", + "const calculateStats = (count = 0, ratio = 0.5, enabled = false, tags = []) => {\n", + " return count * ratio;\n", + "};\n", + "\n", + "// Function with mixed parameter types\n", + "function configureApp(settings = {}, port = 8080, debug = true, version = \"1.0.0\") {\n", + " return settings;\n", + "}\n", + "\n", + "// More complex defaults\n", + "const createUser = (\n", + " profile = { name: \"guest\", role: \"user\" },\n", + " permissions = [\"read\"],\n", + " metadata = null,\n", + " timestamp = Date.now()\n", + ") => {\n", + " return profile;\n", + "};\n", + "\n", + "// Function with no defaults - harder to infer types\n", + "function rawFunction(a, b, c) {\n", + " return a + b + c;\n", + "}\n", + "\n", + "// Different ways to declare functions\n", + "var oldStyleFunc = function(x = 10, y = \"hello\") {\n", + " return x + y;\n", + "};\n", + "\n", + "let modernFunc = function(flag = true, data = [1, 2, 3]) {\n", + " return flag ? data : [];\n", + "};\n", + "\n", + "// Functions assigned to object properties\n", + "utils.validator = function(input = \"\", strict = false) {\n", + " return input.length > 0;\n", + "};\n", + "\n", + "obj.nested.helper = function(config = { timeout: 5000, retry: 3 }) {\n", + " return config.timeout;\n", + "};\n", + "\n" + ] + } + ], + "source": [ + "# Let's create comprehensive JavaScript code with various type scenarios\n", + "js_code_with_types = '''\n", + "// Function with typed parameters and default values - we can infer types from defaults\n", + "function processData(name = \"defaultName\", age = 25, isActive = true, score = 3.14) {\n", + " return name + \" is \" + age + \" years old\";\n", + "}\n", + "\n", + "// Arrow function with defaults of different types\n", + "const calculateStats = (count = 0, ratio = 0.5, enabled = false, tags = []) => {\n", + " return count * ratio;\n", + "};\n", + "\n", + "// Function with mixed parameter types\n", + "function configureApp(settings = {}, port = 8080, debug = true, version = \"1.0.0\") {\n", + " return settings;\n", + "}\n", + "\n", + "// More complex defaults\n", + "const createUser = (\n", + " profile = { name: \"guest\", role: \"user\" },\n", + " permissions = [\"read\"],\n", + " metadata = null,\n", + " timestamp = Date.now()\n", + ") => {\n", + " return profile;\n", + "};\n", + "\n", + "// Function with no defaults - harder to infer types\n", + "function rawFunction(a, b, c) {\n", + " return a + b + c;\n", + "}\n", + "\n", + "// Different ways to declare functions\n", + "var oldStyleFunc = function(x = 10, y = \"hello\") {\n", + " return x + y;\n", + "};\n", + "\n", + "let modernFunc = function(flag = true, data = [1, 2, 3]) {\n", + " return flag ? data : [];\n", + "};\n", + "\n", + "// Functions assigned to object properties\n", + "utils.validator = function(input = \"\", strict = false) {\n", + " return input.length > 0;\n", + "};\n", + "\n", + "obj.nested.helper = function(config = { timeout: 5000, retry: 3 }) {\n", + " return config.timeout;\n", + "};\n", + "'''\n", + "\n", + "print(\"JavaScript code with various type scenarios:\")\n", + "print(js_code_with_types)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0d07052", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Basic Function Extraction (Current jy capability) ===\n", + "\n", + "Function: processData\n", + " - {'name': 'name', 'default': 'defaultName'}\n", + " - {'name': 'age', 'default': 25}\n", + " - {'name': 'isActive', 'default': True}\n", + " - {'name': 'score', 'default': 3.14}\n", + "\n", + "Function: calculateStats\n", + " - {'name': 'count', 'default': 0}\n", + " - {'name': 'ratio', 'default': 0.5}\n", + " - {'name': 'enabled', 'default': False}\n", + " - {'name': 'tags', 'default': None}\n", + "\n", + "Function: configureApp\n", + " - {'name': 'settings', 'default': None}\n", + " - {'name': 'port', 'default': 8080}\n", + " - {'name': 'debug', 'default': True}\n", + " - {'name': 'version', 'default': '1.0.0'}\n", + "\n", + "Function: createUser\n", + " - {'name': 'profile', 'default': None}\n", + " - {'name': 'permissions', 'default': None}\n", + " - {'name': 'metadata', 'default': None}\n", + " - {'name': 'timestamp', 'default': None}\n", + "\n", + "Function: rawFunction\n", + " - {'name': 'a'}\n", + " - {'name': 'b'}\n", + " - {'name': 'c'}\n", + "\n", + "Function: oldStyleFunc\n", + " - {'name': 'x', 'default': 10}\n", + " - {'name': 'y', 'default': 'hello'}\n", + "\n", + "Function: modernFunc\n", + " - {'name': 'flag', 'default': True}\n", + " - {'name': 'data', 'default': None}\n", + "\n", + "Function: utils.validator\n", + " - {'name': 'input', 'default': ''}\n", + " - {'name': 'strict', 'default': False}\n", + "\n", + "Function: obj.nested.helper\n", + " - {'name': 'config', 'default': None}\n" + ] + } + ], + "source": [ + "# Extract function names and parameters using jy\n", + "from jy import func_name_and_params_pairs\n", + "\n", + "print(\"=== Basic Function Extraction (Current jy capability) ===\")\n", + "for func_name, params in func_name_and_params_pairs(js_code_with_types):\n", + " print(f\"\\nFunction: {func_name}\")\n", + " for param in params:\n", + " print(f\" - {param}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "078d6c77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Enhanced Function Parsing with Type Inference ===\n", + "\n", + "Function: processData\n", + " - name: (string - default_value) = defaultName\n", + " - age: (number - default_value) = 25\n", + " - isActive: (boolean - default_value) = True\n", + " - score: (number - default_value) = 3.14\n", + "\n", + "Function: calculateStats\n", + " - count: (number - default_value) = 0\n", + " - ratio: (number - default_value) = 0.5\n", + " - enabled: (boolean - default_value) = False\n", + " - tags: (any - default_value) = None\n", + "\n", + "Function: configureApp\n", + " - settings: (any - default_value) = None\n", + " - port: (number - default_value) = 8080\n", + " - debug: (boolean - default_value) = True\n", + " - version: (string - default_value) = 1.0.0\n", + "\n", + "Function: createUser\n", + " - profile: (any - default_value) = None\n", + " - permissions: (any - default_value) = None\n", + " - metadata: (any - default_value) = None\n", + " - timestamp: (any - default_value) = None\n", + "\n", + "Function: rawFunction\n", + " - a: (any - name_heuristic)\n", + " - b: (any - name_heuristic)\n", + " - c: (any - name_heuristic)\n", + "\n", + "Function: oldStyleFunc\n", + " - x: (number - default_value) = 10\n", + " - y: (string - default_value) = hello\n", + "\n", + "Function: modernFunc\n", + " - flag: (boolean - default_value) = True\n", + " - data: (any - default_value) = None\n", + "\n", + "Function: utils.validator\n", + " - input: (string - default_value) = \n", + " - strict: (boolean - default_value) = False\n", + "\n", + "Function: obj.nested.helper\n", + " - config: (any - default_value) = None\n" + ] + } + ], + "source": [ + "# Now let's create a type inference system that builds on jy\n", + "def infer_type_from_default(default_value):\n", + " \"\"\"Infer JavaScript type from default value\"\"\"\n", + " if default_value is None:\n", + " return \"any\" # Could be null, undefined, or not parseable\n", + " elif isinstance(default_value, bool):\n", + " return \"boolean\"\n", + " elif isinstance(default_value, int):\n", + " return \"number\" # JavaScript doesn't distinguish int from float\n", + " elif isinstance(default_value, float):\n", + " return \"number\"\n", + " elif isinstance(default_value, str):\n", + " return \"string\"\n", + " elif isinstance(default_value, list):\n", + " return \"array\"\n", + " elif isinstance(default_value, dict):\n", + " return \"object\"\n", + " else:\n", + " return \"unknown\"\n", + "\n", + "def infer_type_from_name(param_name):\n", + " \"\"\"Infer type from parameter name patterns (heuristic approach)\"\"\"\n", + " name_lower = param_name.lower()\n", + " \n", + " # Common naming patterns\n", + " if any(word in name_lower for word in ['is', 'has', 'can', 'should', 'enabled', 'active', 'flag', 'debug']):\n", + " return \"boolean\"\n", + " elif any(word in name_lower for word in ['count', 'size', 'length', 'age', 'port', 'timeout', 'retry']):\n", + " return \"number\"\n", + " elif any(word in name_lower for word in ['name', 'title', 'message', 'text', 'input', 'output', 'version']):\n", + " return \"string\"\n", + " elif any(word in name_lower for word in ['list', 'items', 'tags', 'permissions', 'data']):\n", + " return \"array\"\n", + " elif any(word in name_lower for word in ['config', 'settings', 'options', 'profile', 'metadata']):\n", + " return \"object\"\n", + " else:\n", + " return \"any\"\n", + "\n", + "def enhanced_func_parsing(js_code):\n", + " \"\"\"Enhanced function parsing with type inference\"\"\"\n", + " functions = []\n", + " \n", + " for func_name, params in func_name_and_params_pairs(js_code):\n", + " enhanced_params = []\n", + " \n", + " for param in params:\n", + " enhanced_param = param.copy()\n", + " \n", + " # Try to infer type from default value first\n", + " if 'default' in param:\n", + " enhanced_param['type'] = infer_type_from_default(param['default'])\n", + " enhanced_param['type_source'] = 'default_value'\n", + " else:\n", + " # Fall back to name-based inference\n", + " enhanced_param['type'] = infer_type_from_name(param['name'])\n", + " enhanced_param['type_source'] = 'name_heuristic'\n", + " \n", + " enhanced_params.append(enhanced_param)\n", + " \n", + " functions.append({\n", + " 'name': func_name,\n", + " 'parameters': enhanced_params\n", + " })\n", + " \n", + " return functions\n", + "\n", + "print(\"=== Enhanced Function Parsing with Type Inference ===\")\n", + "enhanced_functions = enhanced_func_parsing(js_code_with_types)\n", + "\n", + "for func in enhanced_functions:\n", + " print(f\"\\nFunction: {func['name']}\")\n", + " for param in func['parameters']:\n", + " type_info = f\"({param['type']} - {param['type_source']})\"\n", + " if 'default' in param:\n", + " print(f\" - {param['name']}: {type_info} = {param['default']}\")\n", + " else:\n", + " print(f\" - {param['name']}: {type_info}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e98685c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TypeScript code with explicit types:\n", + "\n", + "interface UserProfile {\n", + " name: string;\n", + " age: number;\n", + " isActive: boolean;\n", + " tags: string[];\n", + " metadata?: object;\n", + "}\n", + "\n", + "function processUser(profile: UserProfile, options: {strict: boolean, timeout: number}): string {\n", + " return profile.name;\n", + "}\n", + "\n", + "const calculateScore = (\n", + " baseScore: number, \n", + " multiplier: number = 1.0, \n", + " bonus?: number\n", + "): number => {\n", + " return baseScore * multiplier + (bonus || 0);\n", + "};\n", + "\n", + "// Mixed TypeScript and JavaScript\n", + "function mixedFunction(\n", + " id: string,\n", + " count = 10, // Type inferred from default\n", + " enabled: boolean = true,\n", + " data?: any[]\n", + "): Promise {\n", + " return Promise.resolve(id);\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "# Let's look at TypeScript parsing for explicit type information\n", + "typescript_code = '''\n", + "interface UserProfile {\n", + " name: string;\n", + " age: number;\n", + " isActive: boolean;\n", + " tags: string[];\n", + " metadata?: object;\n", + "}\n", + "\n", + "function processUser(profile: UserProfile, options: {strict: boolean, timeout: number}): string {\n", + " return profile.name;\n", + "}\n", + "\n", + "const calculateScore = (\n", + " baseScore: number, \n", + " multiplier: number = 1.0, \n", + " bonus?: number\n", + "): number => {\n", + " return baseScore * multiplier + (bonus || 0);\n", + "};\n", + "\n", + "// Mixed TypeScript and JavaScript\n", + "function mixedFunction(\n", + " id: string,\n", + " count = 10, // Type inferred from default\n", + " enabled: boolean = true,\n", + " data?: any[]\n", + "): Promise {\n", + " return Promise.resolve(id);\n", + "}\n", + "'''\n", + "\n", + "print(\"TypeScript code with explicit types:\")\n", + "print(typescript_code)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "901d0a2d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== TypeScript Parsing Available ===\n", + "Error in TypeScript parsing: __init__() takes exactly 1 argument (2 given)\n", + "TypeScript parsing might require additional setup\n" + ] + } + ], + "source": [ + "# Let's try the TypeScript parsing functions from jy\n", + "try:\n", + " from jy import parse_ts\n", + " print(\"=== TypeScript Parsing Available ===\")\n", + " \n", + " # This requires tree-sitter which may not be installed\n", + " ts_items = list(parse_ts(typescript_code))\n", + " for name, info in ts_items:\n", + " print(f\"Name: {name}\")\n", + " print(f\"Info: {info}\")\n", + " print()\n", + " \n", + "except ImportError as e:\n", + " print(f\"TypeScript parsing not available: {e}\")\n", + " print(\"You would need to install: pip install tree-sitter tree-sitter-languages\")\n", + "except Exception as e:\n", + " print(f\"Error in TypeScript parsing: {e}\")\n", + " print(\"TypeScript parsing might require additional setup\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1ab5376", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Enhanced AST-based Type Analysis ===\n", + "\n", + "Function: processData (FunctionDeclaration)\n", + " - name: string = defaultName\n", + " - age: number = 25\n", + " - isActive: boolean = True\n", + " - score: number = 3.14\n", + "\n", + "Function: configureApp (FunctionDeclaration)\n", + " - settings: object = object\n", + " - port: number = 8080\n", + " - debug: boolean = True\n", + " - version: string = 1.0.0\n", + "\n", + "Function: rawFunction (FunctionDeclaration)\n", + " - a: any\n", + " - b: any\n", + " - c: any\n" + ] + } + ], + "source": [ + "# Let's create a more advanced type inference system using the AST from esprima\n", + "from jy.js_parse import parse_js\n", + "import esprima\n", + "\n", + "def extract_complex_defaults(js_code):\n", + " \"\"\"Extract more detailed information about default values from the AST\"\"\"\n", + " ast = parse_js(js_code)\n", + " \n", + " def analyze_literal(node):\n", + " \"\"\"Analyze a literal node to get its type and value\"\"\"\n", + " if hasattr(node, 'type'):\n", + " if node.type == 'Literal':\n", + " value = node.value\n", + " if isinstance(value, bool):\n", + " return {'type': 'boolean', 'value': value}\n", + " elif isinstance(value, (int, float)):\n", + " return {'type': 'number', 'value': value}\n", + " elif isinstance(value, str):\n", + " return {'type': 'string', 'value': value}\n", + " elif value is None:\n", + " return {'type': 'null', 'value': None}\n", + " elif node.type == 'ArrayExpression':\n", + " return {'type': 'array', 'value': 'array'}\n", + " elif node.type == 'ObjectExpression':\n", + " return {'type': 'object', 'value': 'object'}\n", + " elif node.type == 'CallExpression':\n", + " # Handle things like Date.now()\n", + " if (hasattr(node, 'callee') and \n", + " hasattr(node.callee, 'type') and \n", + " node.callee.type == 'MemberExpression'):\n", + " return {'type': 'function_call', 'value': 'computed'}\n", + " return {'type': 'unknown', 'value': 'unknown'}\n", + " \n", + " def extract_function_info(node):\n", + " \"\"\"Extract function information with enhanced default analysis\"\"\"\n", + " if node.type in ['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']:\n", + " func_name = None\n", + " if hasattr(node, 'id') and node.id:\n", + " func_name = node.id.name\n", + " \n", + " params = []\n", + " if hasattr(node, 'params'):\n", + " for param in node.params:\n", + " param_info = {'name': None, 'type': 'any', 'optional': False}\n", + " \n", + " if param.type == 'Identifier':\n", + " param_info['name'] = param.name\n", + " elif param.type == 'AssignmentPattern':\n", + " # Parameter with default value\n", + " param_info['name'] = param.left.name\n", + " param_info['optional'] = True\n", + " default_analysis = analyze_literal(param.right)\n", + " param_info['type'] = default_analysis['type']\n", + " param_info['default'] = default_analysis['value']\n", + " \n", + " params.append(param_info)\n", + " \n", + " return {'name': func_name, 'params': params, 'node_type': node.type}\n", + " return None\n", + " \n", + " def traverse_ast(node, functions):\n", + " \"\"\"Traverse the AST to find function declarations\"\"\"\n", + " if hasattr(node, 'type'):\n", + " func_info = extract_function_info(node)\n", + " if func_info and func_info['name']:\n", + " functions.append(func_info)\n", + " \n", + " # Traverse child nodes\n", + " if hasattr(node, 'body'):\n", + " if isinstance(node.body, list):\n", + " for child in node.body:\n", + " traverse_ast(child, functions)\n", + " else:\n", + " traverse_ast(node.body, functions)\n", + " \n", + " # Handle other node properties that might contain child nodes\n", + " for attr_name in ['expression', 'left', 'right', 'init', 'declarations']:\n", + " if hasattr(node, attr_name):\n", + " attr_value = getattr(node, attr_name)\n", + " if attr_value:\n", + " if isinstance(attr_value, list):\n", + " for item in attr_value:\n", + " traverse_ast(item, functions)\n", + " else:\n", + " traverse_ast(attr_value, functions)\n", + " \n", + " functions = []\n", + " traverse_ast(ast, functions)\n", + " return functions\n", + "\n", + "# Test our enhanced analysis\n", + "print(\"=== Enhanced AST-based Type Analysis ===\")\n", + "enhanced_analysis = extract_complex_defaults(js_code_with_types)\n", + "\n", + "for func_info in enhanced_analysis:\n", + " print(f\"\\nFunction: {func_info['name']} ({func_info['node_type']})\")\n", + " for param in func_info['params']:\n", + " if 'default' in param:\n", + " print(f\" - {param['name']}: {param['type']} = {param['default']}\")\n", + " else:\n", + " print(f\" - {param['name']}: {param['type']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e255ab3a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Comprehensive Type Analysis ===\n", + "\n", + "Function: processData\n", + "Metadata: {'total_params': 4, 'optional_params': 4, 'required_params': 0}\n", + " - name: string = defaultName [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'string', 'from_name': 'string'}\n", + " - age: number = 25 [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'number', 'from_name': 'number'}\n", + " - isActive: boolean = True [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'boolean', 'from_name': 'boolean'}\n", + " - score: number = 3.14 [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'number', 'from_name': 'number'}\n", + "\n", + "Function: calculateStats\n", + "Metadata: {'total_params': 4, 'optional_params': 4, 'required_params': 0}\n", + " - count: number = 0 [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'number', 'from_name': 'number'}\n", + " - ratio: number = 0.5 [optional] (confidence: 0.9)\n", + " Type inference: {'from_default': 'number', 'from_name': 'any'}\n", + " - enabled: boolean = False [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'boolean', 'from_name': 'boolean'}\n", + " - tags: array = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'array'}\n", + "\n", + "Function: configureApp\n", + "Metadata: {'total_params': 4, 'optional_params': 4, 'required_params': 0}\n", + " - settings: object = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'object'}\n", + " - port: number = 8080 [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'number', 'from_name': 'number'}\n", + " - debug: boolean = True [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'boolean', 'from_name': 'boolean'}\n", + " - version: string = 1.0.0 [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'string', 'from_name': 'string'}\n", + "\n", + "Function: createUser\n", + "Metadata: {'total_params': 4, 'optional_params': 4, 'required_params': 0}\n", + " - profile: object = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'object'}\n", + " - permissions: boolean = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'boolean'}\n", + " - metadata: array = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'array'}\n", + " - timestamp: any = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'any'}\n", + "\n", + "Function: rawFunction\n", + "Metadata: {'total_params': 3, 'optional_params': 0, 'required_params': 3}\n", + " - a: any [required] (confidence: 0.5)\n", + " Type inference: {'from_name': 'any'}\n", + " - b: any [required] (confidence: 0.5)\n", + " Type inference: {'from_name': 'any'}\n", + " - c: any [required] (confidence: 0.5)\n", + " Type inference: {'from_name': 'any'}\n", + "\n", + "Function: oldStyleFunc\n", + "Metadata: {'total_params': 2, 'optional_params': 2, 'required_params': 0}\n", + " - x: number = 10 [optional] (confidence: 0.9)\n", + " Type inference: {'from_default': 'number', 'from_name': 'any'}\n", + " - y: string = hello [optional] (confidence: 0.9)\n", + " Type inference: {'from_default': 'string', 'from_name': 'any'}\n", + "\n", + "Function: modernFunc\n", + "Metadata: {'total_params': 2, 'optional_params': 2, 'required_params': 0}\n", + " - flag: boolean = True [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'boolean', 'from_name': 'boolean'}\n", + " - data: array = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'array'}\n", + "\n", + "Function: utils.validator\n", + "Metadata: {'total_params': 2, 'optional_params': 2, 'required_params': 0}\n", + " - input: string = [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'string', 'from_name': 'string'}\n", + " - strict: boolean = False [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'boolean', 'from_name': 'boolean'}\n", + "\n", + "Function: obj.nested.helper\n", + "Metadata: {'total_params': 1, 'optional_params': 1, 'required_params': 0}\n", + " - config: object = None [optional] (confidence: 1.0)\n", + " Type inference: {'from_default': 'any', 'from_name': 'object'}\n" + ] + } + ], + "source": [ + "# Let's create a complete type inference system that combines multiple approaches\n", + "class JavaScriptTypeInference:\n", + " \"\"\"\n", + " A comprehensive JavaScript type inference system that combines:\n", + " 1. Default value analysis\n", + " 2. Parameter name heuristics \n", + " 3. AST analysis\n", + " 4. Pattern recognition\n", + " \"\"\"\n", + " \n", + " def __init__(self):\n", + " self.type_patterns = {\n", + " 'boolean': ['is', 'has', 'can', 'should', 'enabled', 'active', 'flag', 'debug', 'strict'],\n", + " 'number': ['count', 'size', 'length', 'age', 'port', 'timeout', 'retry', 'index', 'id', 'score'],\n", + " 'string': ['name', 'title', 'message', 'text', 'input', 'output', 'version', 'url', 'path'],\n", + " 'array': ['list', 'items', 'tags', 'permissions', 'data', 'values', 'results'],\n", + " 'object': ['config', 'settings', 'options', 'profile', 'metadata', 'params']\n", + " }\n", + " \n", + " def infer_from_default(self, default_value):\n", + " \"\"\"Infer type from default value\"\"\"\n", + " if default_value is None:\n", + " return \"any\"\n", + " elif isinstance(default_value, bool):\n", + " return \"boolean\"\n", + " elif isinstance(default_value, (int, float)):\n", + " return \"number\"\n", + " elif isinstance(default_value, str):\n", + " return \"string\"\n", + " elif isinstance(default_value, list):\n", + " return \"array\"\n", + " elif isinstance(default_value, dict):\n", + " return \"object\"\n", + " else:\n", + " return \"unknown\"\n", + " \n", + " def infer_from_name(self, param_name):\n", + " \"\"\"Infer type from parameter name using patterns\"\"\"\n", + " name_lower = param_name.lower()\n", + " \n", + " for type_name, patterns in self.type_patterns.items():\n", + " if any(pattern in name_lower for pattern in patterns):\n", + " return type_name\n", + " \n", + " return \"any\"\n", + " \n", + " def analyze_functions(self, js_code):\n", + " \"\"\"Complete function analysis with type inference\"\"\"\n", + " from jy import func_name_and_params_pairs\n", + " \n", + " functions = []\n", + " \n", + " for func_name, params in func_name_and_params_pairs(js_code):\n", + " enhanced_function = {\n", + " 'name': func_name,\n", + " 'parameters': [],\n", + " 'metadata': {\n", + " 'total_params': len(params),\n", + " 'optional_params': sum(1 for p in params if 'default' in p),\n", + " 'required_params': sum(1 for p in params if 'default' not in p)\n", + " }\n", + " }\n", + " \n", + " for param in params:\n", + " enhanced_param = {\n", + " 'name': param['name'],\n", + " 'required': 'default' not in param,\n", + " 'type_inference': {}\n", + " }\n", + " \n", + " # Add default value if present\n", + " if 'default' in param:\n", + " enhanced_param['default'] = param['default']\n", + " \n", + " # Multiple type inference approaches\n", + " if 'default' in param:\n", + " default_type = self.infer_from_default(param['default'])\n", + " enhanced_param['type_inference']['from_default'] = default_type\n", + " enhanced_param['type'] = default_type # Primary type\n", + " else:\n", + " enhanced_param['type'] = \"any\"\n", + " \n", + " name_type = self.infer_from_name(param['name'])\n", + " enhanced_param['type_inference']['from_name'] = name_type\n", + " \n", + " # If no default, use name-based inference as primary\n", + " if enhanced_param['type'] == \"any\" and name_type != \"any\":\n", + " enhanced_param['type'] = name_type\n", + " \n", + " # Confidence scoring\n", + " confidence = 0.9 if 'default' in param else 0.3\n", + " if enhanced_param['type_inference']['from_name'] == enhanced_param['type']:\n", + " confidence += 0.2\n", + " enhanced_param['type_confidence'] = min(confidence, 1.0)\n", + " \n", + " enhanced_function['parameters'].append(enhanced_param)\n", + " \n", + " functions.append(enhanced_function)\n", + " \n", + " return functions\n", + " \n", + " def generate_typescript_interface(self, function_analysis):\n", + " \"\"\"Generate TypeScript interface definitions from analysis\"\"\"\n", + " interfaces = []\n", + " \n", + " for func in function_analysis:\n", + " interface_name = f\"{func['name'].replace('.', '_').title()}Params\"\n", + " \n", + " interface_def = f\"interface {interface_name} {{\\n\"\n", + " for param in func['parameters']:\n", + " optional_marker = \"?\" if not param['required'] else \"\"\n", + " interface_def += f\" {param['name']}{optional_marker}: {param['type']};\\n\"\n", + " interface_def += \"}\"\n", + " \n", + " interfaces.append({\n", + " 'function': func['name'],\n", + " 'interface_name': interface_name,\n", + " 'definition': interface_def\n", + " })\n", + " \n", + " return interfaces\n", + "\n", + "# Test our comprehensive system\n", + "type_inferrer = JavaScriptTypeInference()\n", + "comprehensive_analysis = type_inferrer.analyze_functions(js_code_with_types)\n", + "\n", + "print(\"=== Comprehensive Type Analysis ===\")\n", + "for func in comprehensive_analysis:\n", + " print(f\"\\nFunction: {func['name']}\")\n", + " print(f\"Metadata: {func['metadata']}\")\n", + " for param in func['parameters']:\n", + " req_str = \"required\" if param['required'] else \"optional\"\n", + " conf_str = f\"(confidence: {param['type_confidence']:.1f})\"\n", + " if 'default' in param:\n", + " print(f\" - {param['name']}: {param['type']} = {param['default']} [{req_str}] {conf_str}\")\n", + " else:\n", + " print(f\" - {param['name']}: {param['type']} [{req_str}] {conf_str}\")\n", + " print(f\" Type inference: {param['type_inference']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2220d333", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Generated TypeScript Interfaces ===\n", + "\n", + "For function: processData\n", + "interface ProcessdataParams {\n", + " name?: string;\n", + " age?: number;\n", + " isActive?: boolean;\n", + " score?: number;\n", + "}\n", + "\n", + "For function: calculateStats\n", + "interface CalculatestatsParams {\n", + " count?: number;\n", + " ratio?: number;\n", + " enabled?: boolean;\n", + " tags?: array;\n", + "}\n", + "\n", + "For function: configureApp\n", + "interface ConfigureappParams {\n", + " settings?: object;\n", + " port?: number;\n", + " debug?: boolean;\n", + " version?: string;\n", + "}\n", + "\n", + "=== Return Type Inference Ideas ===\n", + "\n", + "For return type inference, we could analyze:\n", + "\n", + "1. Return statements in function bodies:\n", + " - return \"string\" → string\n", + " - return 42 → number \n", + " - return true → boolean\n", + " - return [] → array\n", + " - return {} → object\n", + "\n", + "2. Function name patterns:\n", + " - Functions starting with 'is', 'has', 'can' → boolean\n", + " - Functions with 'count', 'calculate' → number\n", + " - Functions with 'get', 'find' → depends on context\n", + " - Functions with 'create', 'build' → object\n", + "\n", + "3. JSDoc comments:\n", + " - @returns {string} → string\n", + " - @returns {Promise} → Promise\n", + "\n", + "4. TypeScript annotations:\n", + " - function foo(): string → string\n", + " - async function bar(): Promise → Promise\n", + "\n" + ] + } + ], + "source": [ + "# Generate TypeScript interfaces from our analysis\n", + "print(\"\\n=== Generated TypeScript Interfaces ===\")\n", + "typescript_interfaces = type_inferrer.generate_typescript_interface(comprehensive_analysis)\n", + "\n", + "for interface in typescript_interfaces[:3]: # Show first 3 to keep output manageable\n", + " print(f\"\\nFor function: {interface['function']}\")\n", + " print(interface['definition'])\n", + "\n", + "# Show return type inference potential\n", + "print(\"\\n=== Return Type Inference Ideas ===\")\n", + "print(\"\"\"\n", + "For return type inference, we could analyze:\n", + "\n", + "1. Return statements in function bodies:\n", + " - return \"string\" → string\n", + " - return 42 → number \n", + " - return true → boolean\n", + " - return [] → array\n", + " - return {} → object\n", + "\n", + "2. Function name patterns:\n", + " - Functions starting with 'is', 'has', 'can' → boolean\n", + " - Functions with 'count', 'calculate' → number\n", + " - Functions with 'get', 'find' → depends on context\n", + " - Functions with 'create', 'build' → object\n", + "\n", + "3. JSDoc comments:\n", + " - @returns {string} → string\n", + " - @returns {Promise} → Promise\n", + "\n", + "4. TypeScript annotations:\n", + " - function foo(): string → string\n", + " - async function bar(): Promise → Promise\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "540f348a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd5a066f", + "metadata": {}, + "outputs": [], + "source": [ + "import rh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28082ad8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3a51d89", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}