Skip to content
This repository has been archived by the owner on Mar 2, 2024. It is now read-only.

New version of ArgueJS #15

Open
wants to merge 77 commits into
base: master
Choose a base branch
from
Open

New version of ArgueJS #15

wants to merge 77 commits into from

Conversation

johmoh
Copy link

@johmoh johmoh commented May 17, 2013

(copy from #14):

Hello,

I made a fork. It is just for early birds and to get response. In the end, I want - with hopefully permission of the maintainer of this project - integrate the code in this repository. Here it is:

https://github.com/johmoh/ArgueJS.git

The module name is "arguejs2" and it is a complete rewrite. The original version is untouched. This version is not highly tested at the moment. But I have a good feeling, that everything works fine. Why the changes... It solves the following feature requests, enhancement requests, bugs and general discussions:

bugs:

enhancements:

feature requests:

general solutions:

concept

The function signature is now defined as array containing objects. These objects specify parameters:

[{...}, {...}, ...]

Each object has the following structure:

{name: typespec}

typespec can be...

  • A type (String, Boolean, MyClass, ...) - for mandatory parameters.
  • An array containing 1 value - the type. That marks the parameter as optional.
  • An array containing 2 values - the type (first value) and a default value (second value). That marks the parameter as optional.
  • An object with the properties (key:value)-pairs:
    • (type:typespec) - This property is mandatory. Typespec must be a type (parameter is mandatory) or an array containing one value - the type (parameter is optional).
    • (defaultValue:ValueOrObjectOrFunction) - This property is optional. If you define it, than the parameter gets marked as optional. Of course you can specify undefined or null as default value. You can also specify a function for a non-function parameter. In that case the function gets called if the default value is needed (and only in that case). If the type of the parameter is "Function" then this function is not called. In that case, the function is the default value itself. In a variadic function the default value must be an array for the tail parameter.
    • (allowUndefined:true|false) - This property is optional (default is false). It is forbidden to specify a default value as undefined, if undefined is not allowed. That means: If you want your parameter to accept undefined or you want to specify a default value as undefined, than you must allow undefined by writing: allowUndefined:true. That parameter is forbidden for the tail parameter in a variadic function.
    • (allowNull:true|false) - The same rules as allowUndefined.
    • (parenthesizeTail:true|false) - This property is optional (default is false) and only allowed for the tail parameter in a variadic function. The parameter answers the following question: If your tail contains only 1 value and that value is an array should I include that value in a new array or should I return that value (= it is already an array) as it is. The reason for that parameter is simple: If you want to call a variadic function and you already have an array of arguments then you simply can pass the array as the tail argument. Example:
formatText() {
  var _args = arguejs2.getArguments([{formatString:String}, {replacements:arguejs2.[TAIL]}], arguments);
  // use _args.replacements to format the _args.formatString
}

If parenthesizeTail:false then you can call formatText with...

var replacements = [...];
var formattedText = formatText(formatString, replacements[0], replacements[1], ...);

...or with...

var replacements = [...];
var formattedText = formatText(formatString, replacements);

Defining "any type" or the tail parameter in a variadic function: ArgueJS2 introduces two types for that: ArgueJS2.ANYTYPE and ArgueJS2.TAIL. So you specify a parameter of anytype that is optional with {myParameter: [arguejs2.ANYTYPE]} or {myParameter: {type: [arguejs2.ANYTYPE]}}. It's the same for the tail parameter of a variadic function. Just replace "ANYTYPE" with "TAIL" in the example.

What does ANYTYPE mean? - It means any type except undefined or null. That means, if you want to specify a parameter to be any type or undefined or null then you have to specify that with allowUndefined:true and allowNull:true:

{myParameter: {type: arguejs2.ANYTYPE, allowUndefined:true, allowNull:true}}

There is a example directory in the repository containing an example file to get started.

Yo, that's it for here and now.
Feedback is welcome.

johmoh added 30 commits May 17, 2013 05:41
…tArguments outside a loop to stop getting warnings from jslint.
…tionality. that function is used in tests of internal functions.
- test of old ArgueJS (version 1) moved from ./test/ to ./spec/ArgueJS1/
- add new directory ./spec/ArgueJS1/ for tests of new ArgueJS (version 2)
- change karma.conf.js so that all tests of ArgueJS version 1 and version 2 run
- karma does only test in Chrome; I didn't get tests to run in Firefox or PhantomJS under Windows (always crashed and at first i have to figure out why that happens)
- tests for ArgueJS2: complete test of internal utility functions for working with types
…rrectly) thrown error did not had an error text (that was the bug)
… was tested, if the parameterType itself is a function (that is alway true) and not if the parameterType equals Function.
…ction then that function is the default value itself. otherwise it wouldn't be possible to pass a function to an argument of type ANYTYPE.
…s in ArgueJS2.__export_internals__. that is used for testing later.
…ts to keep the root directory of the project as clean as possible.
- add dependency to uglify-js to package.json
- add build-minified-version.bat to scripts
- add minified versions of ArgueJS2; the "testable.min"-versions are for unit tests of ArgueJS2 only
license is only valid for ArgueJS2 (argue2.js). I couldn't find any license information for ArgueJS (argue.js).
…ter name is null, undefined or an empty string
…e in a function specification (parameter with name "XYZ" already defined)
- rename "error-spec.js" to "error-detection-spec.js"
- added test cases for new functionality in getArguments(...): test if a parameter is specified more than once in a function specification (parameter with name "XYZ" already defined)
- fixed a bug in test file that caused to have to have more tests succeeded than specified. the bug was an "describe"-statement that was nested in an "it"-statement. took two hours to find the trouble maker. would be a good idea if mocha would print a warnig in such a case.
…js".

these files are versions for production use. in that versions many tests are removed. "for production use" means: code that uses ArgueJS2 that is itself ready for production should use "argue2.production.min.js". but if code that uses ArgueJS2 is in development or in test should NOT use that version.
- "argue2.production.min.js" is the version to use, wenn your code goes live.
- if you are in development or test use "argue2.js" or "argue2.min.js" instead.
- "argue2.testable.production.min.js" is a version the test of ArgueJS2 uses to validate that building that minimized version does not break the functionality of the library. so, it's for internal use only.
…rgueJS2, too. "error-detection-spec.js" does not run test cases for the minimized production version of ArgueJS2 that would test behaviour which is removed by building that special version of ArgurJS2.
@zvictor
Copy link
Owner

zvictor commented May 23, 2013

@johmoh I am impressed with all your work and effort to improve this project. Thank you again!

I liked to see this pull request, but you have put a big decision in my hands. There are a lot of commits that I would approve (e.g. IE compatibility is a must-have!), but some others I would not (e.g. you have created 8 extra argue2.*.js files in your root directory!) without a good discussion.

What to do so? It seems to me that we have very different approaches to design a project and while we don't discuss them, it wont be possible to develop to the same direction. So, I think that we should argue!

Let's schedule a Skype or Hangout?

@johmoh
Copy link
Author

johmoh commented May 23, 2013

Yes, of course, we can Skype or Hangout. But not this night anymore. Now is time for bed. If you are from Brazil and if I am right then you are 5h back in time compared to me. Everything till 2am my local time = 9pm your local time is okay. - I am a creature of the night...

I am relatively new to GitHub. Is there a way to send messages privately?

To all that files...

  • argue2.js = it's the implementation
  • argue2.min.js = it's a minimized version (__ export_internals __ is also removed because it is a non-supported part of the library with only one purpose: make unit testing internal functionality possible)
  • argue2.testable.min.js = same as argue2.min.js but __ export_internals __ is still included - this version is needed for unit testing argue2.min.js - just to make sure that minimizing the implementation did not break functionality.
  • argue2.production.min.js = same as argue2.min.js but additional tests for clients using that library are removed. these removed tests are tests that check if the library is used correctly. these tests have nothing to do with testing a function specification against arguments. Example: The function specification must be an array. That test can be removed if you know for sure that you always call getArguments with an array as function specification. That version is for real production use and expect that version to execute calls to getArguments in half the time the normal minimized or the original version would.
  • argue2.testable.production.min.js = same as argue2.production.min.js but still contains __ export_internals __ to test that this kind of aggressive minimization did not break functionality.
  • argue2.*.min.js.map = all the map files are generated by UglifyJS2. I do not know for sure what these files are for. But I think these files contain information to map a position in the minimized version to a position in the original file. I am totally fine if these map-files get removed. UglifyJS2 can generate them, I do not know exactly what they are for, so I did not remove them - maybe someone knows what these files are for and maybe he is happy that these files exist.

argue.js is untouched. I just added a minimized version (argue.min.js) and a corresponding testable-version. argue.min.js and argue.testable.min.js are equal (with exception of one comment at the end).

summary:

  • argue2.js: here is the implementation; its for the contributors to this project (coding new enhancements, fix bugs, ...)
  • argue2.min.js: is for users of the library - can be used for developing, testing applications and in production
  • argue2.production.min.js: is for users of the library - should only be used in production, if a user of the library has proven his application to work correctly (unit tests, integration tests).
  • argue2.testable.*.js: that versions are for contributors to this project which write unit tests. these files are used to verify that the corresponding min.js-version works exactly as the original implementation (argue2.js). Such a test is necessary because aggressive compression of code can break functionality.
  • argue2.*.js.map: I do not know what these files are for. Maybe one sunny day I am glad that these files exist. If you have a problem with that then lets delete them.

I think maybe it is a good idea...

  • ... to remove the map-files and do not generate these files anymore.
  • ... to move all testable-files in a new directory or to spec/ or to spec/lib or whatever - just to clean up the root directory.
    ...then the root directory is not so bloated anymore.

johmoh added 11 commits May 23, 2013 19:49
- delete all *.min.js.map files
- do not generate *.min.js.map files any more
- move *.testable.*min.js files to spec/ because these files are only relevant in unit tests
- make unit tests work again
… change code to contain no warnings by jshint
…ompatibleDefaultValue to improve readability of the code (better separation of concerns)

reduced long lines to 120 characters except for some well defined exceptions
…etect usage of "console.log"

- rename "DEFAULT_OPTION_VALUE_*" in "DEFAULT_VALUE_FOR_*" because it improves readability
- add internal function getDefaultValue(...). that function replaces a little piece of code in getArguments(...) just to reduce code complexity of getArguments(...).
… isCompatibleValue(...) (does not exist any longer) to reference internal function isCompatibleDefaultValue(...)
…n internal functions. that way getArguments(...) is much easier to understand for others.

recreate documentation to contain new internal functions
@johmoh
Copy link
Author

johmoh commented May 30, 2013

I reduced complexity in getArguments(...) a lot by outsourcing functionality in internal functions. The cyclomatic complexity of getArguments(...) is now reduced from slightly above 100 to 22. I hope that the code is now better understandable for everyone.

…ion(...), isSimpleOptionalTypeSpecification(...) and isComplexTypeSpecification(...)
@kitlee
Copy link

kitlee commented Dec 16, 2015

Thank you for the great work, but how to use this new ArgueJS in node.js directly?

For now, I add a dependency "arguejs" : "https://github.com/johmoh/ArgueJS.git" in my package.json, then npm install and changed "main" : "argue.js" to "main" : "argue2.js" in node_modules\arguejs\package.json.

Can I include the dependency and then npm install to be ready to go? I do not want to deal with the node_modules folder in production environment.

Also, the docs may not be clear enough, I thought the usage is similar to the original ArgueJS, so I wrote:

function add() {
  arguments = argue2([{x : [Number, 10]}, {y : [Number, 100]}]);
  var x = arguments.x;
  var y = arguments.y;
  return x + y;
}

but it should be like this:

function add() {
  var _args = argue2.getArguments([{x : [Number, 10]}, {y : [Number, 100]}], arguments);
  var x = _args.x;
  var y = _args.y;
  return x + y;
}

It did take me a while to figure out. (maybe just me)

@johmoh
Copy link
Author

johmoh commented Dec 17, 2015

@kitlee: Hello,

expect ArgueJS and ArgueJS2 to be dead. But if you find a bug in ArgueJS2 then - of course - I will fix him.

At first I just wanted to fix some issues / bugs in ArgueJS. As it turned out that the usage of the new version became incompatible with ArgueJS, I started calling it ArgueJS2. ArgueJS2 is a complete rewrite.

If you are looking at "how do I use ArgueJS2" then start at the top of this page with reading #15 or with reading #14 (at the top of #15 there is a copy of the text of #14). Moreover you should have a look at examples (https://github.com/johmoh/ArgueJS/tree/master/examples). For instance an "add"-function in https://github.com/johmoh/ArgueJS/blob/master/examples/examples-simple.js:

// show case: two arguments
function add() {
    var _args = arguejs2.getArguments([{a: Number}, {b: Number}], arguments);

    console.log("" + _args.a + " + " + _args.b + " = " + (_args.a + _args.b));
}

In your second code example you "enhanced" that example by using default values.

If you want an integration in NPM then you can do - of course - me a favour: clone my repository, adjust package.json and push the new version to NPM as arguejs2. I will not do that, because my intention was to improve ArgueJS and not to introduce a new NPM module. Moreover there is a better way to check types in JavaScript. Here it is...

My intention to use ArgueJS was, that I wanted a kind of type checking for arguments (I am more a C/C++, C#, Java programmer). So, if that is also your intention then I advise you to use TypeScript (http://www.typescriptlang.org/). TypeScript is a super set of JavaScript. That means any JavaScript program is valid TypeScript. But TypeScript adds some good things that you can use if you want (but you don't have to): interfaces, classes, "generics", compile time type checking, ... The best: every enhancement is optional. There is also a good free IDE with good TypeScript support: Visual Studio Code (https://code.visualstudio.com/). Visual Studio Code runs under Linux, OS X and Windows.

I hope, I could help you. If you still see an existing use case for this library, something that cannot be done with TypeScript, then please let me know.

Best regards,
Martin

Project is dead...
@kitlee
Copy link

kitlee commented Dec 17, 2015

Thank you, but it is sad to hear about that, this plugin is a great idea in JavaScript, I hope someone would keep enhancing the features.

Anyway, is it possible to do something like this?
I have a function :

function myAsyncFunc(options, callback) { ... }

where options and callback are both optional.
If I only pass an object, callback will be the dummy function.
If I only pass a function, options will be an empty object and the function will be callback

but the following code does not seem to be working, the type checking for options is failed

  var args = argue2.getArguments([
    {options : {type : Object, defaultValue : {}}},
    {callback : {type : Function, defaultValue : function () {}}}
  ], arguments);

when I invoke myAsyncFunc(function (err) { ... });, args.options will be the function I passed in, args.callback will be the dummy function.

@johmoh
Copy link
Author

johmoh commented Dec 17, 2015

@kitlee: The behaviour is correct. Here is the reason why: The algorithm tries to match values of arguments from left to right. In you example, the algorithm tries to fulfil the request for an object first and then it tries to fulfil the request for a function. Now you have to keep in mind: Any function in JavaScript is an object BUT not any object in JavaScript is a function. That means in your case, if you pass a function as first parameter, it is used as an object because you requested an object as first parameter.

How to solve your problem:
(a) Switch parameter order to:

var args = argue2.getArguments([
    {callback : {type : Function, defaultValue : function () {}}},
    {options : {type : Object, defaultValue : {}}}
  ], arguments);

(b) I could change function isObject from ...

function isObject(_value) {
    return (_value instanceof Object);
}

... to ...

function isObject(_value) {
    return (_value instanceof Object) && !isFunction(_value);
}

Please try variant (a) and tell me, if that solves your problem (it should). Variant (b) would break the JavaScript rule that any function is a valid object. So, variant (b) is a bad idea (IMHO).

I have not tested variant (a) or (b) yet, but I think both should work.

Even if ArgueJS2 is dead, it is IMHO feature complete and well tested. Supported are:

  • Specification of arguments, types and order of arguments
  • Specification if "null" is allowed as value for an argument
  • Specification if "undefined" is allowed as value for an argument
  • Specification of default values as values (no function)
  • Specification of default values as functions; function gets called to get the default value in case the type of the argument is not "Function"
  • The algorithm to match argument values to specified arguments with default values is way more clever then in other programming languages like Java, C#, C/C++. But it is also more easy to make mistakes.
  • Specification of variadic functions

So "dead" just means "I will not add new features". But I will fix bugs, if there are any and I will answer questions.

I really highly recommend you to at least try out TypeScript. Type-safe programming in TypeScript is optional. You don't have to. But if you do then type checks are done at compile time.

Best regards,
Martin

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants