From 847370290777913b1d7b961be2d0c2aaf7eac77f Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Wed, 8 Nov 2023 01:25:22 +0100 Subject: [PATCH] feat: get argument names --- index.d.ts | 8 +- index.js | 11 ++- lib/_construct_jobject.js | 18 ---- package.json | 2 +- src/parser/mod.rs | 99 ++++++++++++++++++---- src/parser/transformers/optional_import.rs | 2 +- src/wasm/compile.rs | 5 ++ tests/wasm/parser.js | 24 +++++- 8 files changed, 126 insertions(+), 43 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7df5a24..7ab9c04 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,10 @@ -export { compile, isValidIdentifier, start, prepareStackTrace } from './pkg/compiler'; +export { + compile, + getArgumentNames, + isValidIdentifier, + start, + prepareStackTrace, +} from './pkg/compiler'; declare interface JsMethodParameter { name?: String; diff --git a/index.js b/index.js index a06eef5..3ef0a6c 100644 --- a/index.js +++ b/index.js @@ -5,12 +5,17 @@ const isSimdSupported = WebAssembly.validate( 1, 8, 0, 65, 0, 253, 15, 253, 98, 11, ]), ); -const { compile, isValidIdentifier, prepareStackTrace, start } = isSimdSupported - ? require('./simd/compiler') - : require('./pkg/compiler'); +const { + compile, + getArgumentNames, + isValidIdentifier, + prepareStackTrace, + start, +} = isSimdSupported ? require('./simd/compiler') : require('./pkg/compiler'); exports._isSimdSupported = isSimdSupported; exports.compile = compile; +exports.getArgumentNames = getArgumentNames; exports.isValidIdentifier = isValidIdentifier; exports.prepareStackTrace = prepareStackTrace; exports.start = start; diff --git a/lib/_construct_jobject.js b/lib/_construct_jobject.js index 4d190ba..7a1dac4 100644 --- a/lib/_construct_jobject.js +++ b/lib/_construct_jobject.js @@ -21,24 +21,6 @@ exports._ = function _construct_jobject(callee, ...$args) { } let self = r; - if ( - global.__jymfony !== void 0 && - r instanceof __jymfony.JObject && - __jymfony.autoload !== void 0 && - __jymfony.autoload.debug - ) { - Reflect.preventExtensions(self); - // self = new Proxy(self, { - // get: (target, p) => { - // if (p !== Symbol.toStringTag && ! Reflect.has(target, p)) { - // throw new TypeError('Undefined property ' + p.toString() + ' on instance of ' + ReflectionClass.getClassName(target)); - // } - // - // return Reflect.get(target, p); - // }, - // }); - } - if (Reflect.has(r, '__invoke')) { return new Proxy(self.__invoke, { get: (_, key) => { diff --git a/package.json b/package.json index 2e1e439..08f02bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jymfony/compiler", - "version": "0.5.0-beta.1", + "version": "0.5.0-beta.3", "type": "commonjs", "author": "Alessandro Chitolina ", "license": "MIT", diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9f3fc0f..0718b88 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,5 +1,5 @@ use crate::SyntaxError; -use anyhow::Result; +use anyhow::{Error, Result}; pub use program::CompileOptions; use program::Program; use std::path::PathBuf; @@ -8,7 +8,7 @@ use swc_common::comments::SingleThreadedComments; use swc_common::input::StringInput; use swc_common::sync::Lrc; use swc_common::{BytePos, FileName}; -use swc_ecma_ast::EsVersion; +use swc_ecma_ast::{EsVersion, Expr, Pat}; use swc_ecma_parser::lexer::Lexer; use swc_ecma_parser::token::{IdentLike, Token, Word}; use swc_ecma_parser::{EsConfig, Parser, Syntax, TsConfig}; @@ -18,7 +18,19 @@ mod sourcemap; mod transformers; mod util; -pub const ES_VERSION: EsVersion = EsVersion::EsNext; +const ES_VERSION: EsVersion = EsVersion::EsNext; +const ES_CONFIG: EsConfig = EsConfig { + jsx: false, + fn_bind: false, + decorators: true, + decorators_before_export: false, + export_default_from: false, + import_attributes: true, + allow_super_outside_method: false, + allow_return_outside_function: true, + auto_accessors: true, + explicit_resource_management: true, +}; pub trait CodeParser { fn parse_program(self, filename: Option<&str>) -> Result; @@ -51,18 +63,7 @@ impl CodeParser for &str { disallow_ambiguous_jsx_like: false, }) } else { - Syntax::Es(EsConfig { - jsx: false, - fn_bind: false, - decorators: true, - decorators_before_export: false, - export_default_from: false, - import_attributes: true, - allow_super_outside_method: false, - allow_return_outside_function: true, - auto_accessors: true, - explicit_resource_management: true, - }) + Syntax::Es(ES_CONFIG) }; let lexer = Lexer::new( @@ -100,8 +101,8 @@ impl CodeParser for &str { pub fn is_valid_identifier(input: &str) -> bool { let lexer = Lexer::new( - Default::default(), - EsVersion::EsNext, + Syntax::Es(ES_CONFIG), + ES_VERSION, StringInput::new(input, BytePos(0), BytePos(input.len() as u32)), None, ); @@ -119,9 +120,43 @@ pub fn is_valid_identifier(input: &str) -> bool { } } +fn process_pat(p: &Pat) -> String { + match p { + Pat::Ident(i) => i.sym.to_string(), + Pat::Rest(r) => process_pat(r.arg.as_ref()), + Pat::Assign(a) => process_pat(a.left.as_ref()), + _ => Default::default(), + } +} + +pub fn get_argument_names(input: &str) -> Result> { + let lexer = Lexer::new( + Syntax::Es(ES_CONFIG), + ES_VERSION, + StringInput::new(input, BytePos(0), BytePos(input.len() as u32)), + None, + ); + + let mut parser = Parser::new_from(lexer); + let expr = parser + .parse_expr() + .map_err(|e| Error::msg(e.kind().msg()))?; + + match expr.as_ref() { + Expr::Arrow(arrow) => Ok(arrow.params.iter().map(process_pat).collect()), + Expr::Fn(func) => Ok(func + .function + .params + .iter() + .map(|p| process_pat(&p.pat)) + .collect()), + _ => Err(Error::msg("not a function expression")), + } +} + #[cfg(test)] mod tests { - use super::{is_valid_identifier, CodeParser}; + use super::{get_argument_names, is_valid_identifier, CodeParser}; use crate::parser::transformers::decorator_2022_03; use crate::testing::exec_tr; use crate::testing::uuid::reset_test_uuid; @@ -427,4 +462,32 @@ export default @logger.logged class x { assert!(is_valid_identifier("y")); assert!(is_valid_identifier("ident")); } + + #[test] + pub fn should_return_function_identifier() { + assert_eq!( + Vec::<&str>::new(), + get_argument_names(r#"function() {}"#).unwrap() + ); + assert_eq!( + vec!["", "args"], + get_argument_names(r#"function([a, b], args) {}"#).unwrap() + ); + assert_eq!( + vec!["context"], + get_argument_names(r#"function(context = {}) {}"#).unwrap() + ); + assert_eq!( + vec!["obj"], + get_argument_names(r#"function(...obj = []) {}"#).unwrap() + ); + assert_eq!( + vec!["context"], + get_argument_names(r#"(context = {}) => {}"#).unwrap() + ); + assert_eq!(vec!["arg"], get_argument_names(r#"arg => arg"#).unwrap()); + + assert!(get_argument_names(r#"class x {}"#).is_err()); + assert!(get_argument_names(r#"module.exports = function () {}"#).is_err()); + } } diff --git a/src/parser/transformers/optional_import.rs b/src/parser/transformers/optional_import.rs index 503bfb1..419232d 100644 --- a/src/parser/transformers/optional_import.rs +++ b/src/parser/transformers/optional_import.rs @@ -89,7 +89,7 @@ impl VisitMut for OptionalImport { kv.key.as_ident().is_some_and(|i| i.sym == "optional") && kv.value.as_lit().is_some_and(|l| match l { Lit::Bool(b) => b.value, - Lit::Str(s) => s.value.to_string() == "true", + Lit::Str(s) => s.value == "true", _ => false, }) }) diff --git a/src/wasm/compile.rs b/src/wasm/compile.rs index 8cf17a1..4a299a7 100644 --- a/src/wasm/compile.rs +++ b/src/wasm/compile.rs @@ -73,3 +73,8 @@ pub fn is_valid_identifier(input: JsValue) -> bool { false } } + +#[wasm_bindgen(js_name = getArgumentNames)] +pub fn get_argument_names(input: String) -> Result, JsError> { + crate::parser::get_argument_names(&input).map_err(|e| JsError::new(&e.to_string())) +} diff --git a/tests/wasm/parser.js b/tests/wasm/parser.js index a46be19..6c93d18 100644 --- a/tests/wasm/parser.js +++ b/tests/wasm/parser.js @@ -1,4 +1,4 @@ -const { isValidIdentifier } = require('../..'); +const { isValidIdentifier, getArgumentNames } = require('../..'); describe('Parser', () => { const identifiers = ['x', 'y', 'ident']; @@ -18,4 +18,26 @@ describe('Parser', () => { }, ); } + + it('should return function parameters names', () => { + const noArg = function () {}; + const arrArg = function ([a, b], args) {}; + const contextArg = function (context = {}) {}; + const restArg = function (...obj) {}; + const arrowContext = (context = {}) => {}; + const arrowNoParens = (arg) => arg; + + expect(getArgumentNames('function() {}')).toEqual([]); + expect(getArgumentNames(noArg.toString())).toEqual([]); + expect(getArgumentNames(arrArg.toString())).toEqual(['', 'args']); + expect(getArgumentNames(contextArg.toString())).toEqual(['context']); + expect(getArgumentNames(restArg.toString())).toEqual(['obj']); + expect(getArgumentNames(arrowContext.toString())).toEqual(['context']); + expect(getArgumentNames(arrowNoParens.toString())).toEqual(['arg']); + + expect(() => getArgumentNames('class x {}')).toThrowError(); + expect(() => + getArgumentNames('module.exports = function () {}'), + ).toThrowError(); + }); });