Beispiel #1
0
 private static string Replace(string source, JsRegex regEx, string replaceWith)
 {
     return(Script.Write <string>("{0}.replace({1}, {2})", source, regEx, replaceWith));
 }
Beispiel #2
0
        private static PropertySetter ConstructSetter <T, TPropertyValue>(this T source, Func <T, TPropertyValue> propertyIdentifier)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (propertyIdentifier == null)
            {
                throw new ArgumentNullException("propertyIdentifier");
            }

            if (GetFunctionArgumentCount(propertyIdentifier) != 1)
            {
                throw new ArgumentException("The specified propertyIdentifier function must have precisely one argument");
            }

            // Ensure that the propertyIdentifier is of a form similar to
            //   function (_) { return _.getName(); }
            // Note that in minified JavaScript, it may be
            //   function(_){return _.getName()}
            // The way that this is verified is to get a "normalised" version of the string representation of the propertyIdentifier function - this removes any comments
            // and replaces any single whitespace characters (line returns, tabs, whatever) with a space and ensures that any runs of whitespace are reduced to a single
            // character. We compare this (with a reg ex) to an expected format which is a string that is built up to match the first form. This is then tweaked to make
            // the spaces and semi-colon optional (so that it can also match the minified form).
            // 2016-08-04 DWR: There are some additional forms that should be supported; for example, Firefox may report "use strict" as part of the function content -
            //   function (_) { "use strict"; return _.getName(); }
            // .. and code coverage tools may inject other content before the getter is called -
            //   function (_) { coverageFramework.track("MyClass.cs", 18); return _.getName(); }
            // .. as such, the function format matching has been relaxed to allow a section to be ignored before the getter call. I contempled removing this entirely since
            // there is an analyser to ensure that only valid properties are referenced in the C# code but this change seemed minor and could be useful if a project included
            // IAmImmutable implementations that disabled the analyser (or that were built in VS2013 or earlier). If there are any further problems then I may reconsider.
            var singleArgumentNameForPropertyIdentifier = GetFunctionSingleArgumentName(propertyIdentifier);

            // In case there are any new lines in the additional content that is supported before the getter call (see 2016-08-04 notes above), we need to look for "any
            // character" that includes line returns and so use "[.\s\S]*" instead of just ".*" (see http://trentrichardson.com/2012/07/13/5-must-know-javascript-regex-tips/).
            // It might be cleaner to use the C# RegEx which is fully supported by Bridge (but wasn't when this code was first written).
            var argumentName = GetFunctionSingleArgumentName(propertyIdentifier);

            // If an IAmImmutable type is also decorated with [ObjectLiteral] then instances won't actually have real getter and setter methods, they will just have raw
            // properties. There are some hoops to jump through to combine IAmImmutable and [ObjectLiteral] (the constructor won't be called and so CtorSet can't be used
            // to initialise the instance) but if this combination is required then the "With" method may still be used by identifying whether the current object is a
            // "plain object" and working directly on the property value if so.
            var isObjectLiteral = IsObjectLiteral(source);

            if (isObjectLiteral)
            {
                var objectLiteralRegExSegments = new[] {
                    AsRegExSegment(string.Format(
                                       "function ({0}) {{",
                                       argumentName
                                       )),
                    AsRegExSegment(string.Format(
                                       "return {0}.",
                                       argumentName
                                       )),
                    AsRegExSegment("; }")
                };
                var objectLiteralExpectedFunctionFormatMatcher = new JsRegex(
                    string.Join("([.\\s\\S]*?)", objectLiteralRegExSegments)
                    );
                var objectLiteralPropertyIdentifierStringContent = GetNormalisedFunctionStringRepresentation(propertyIdentifier);
                var objectLiteralPropertyNameMatchResults        = objectLiteralExpectedFunctionFormatMatcher.Exec(objectLiteralPropertyIdentifierStringContent);
                if (objectLiteralPropertyNameMatchResults == null)
                {
                    throw new ArgumentException("The specified propertyIdentifier function did not match the expected format - must be a simple property access for an [ObjectLiteral], such as \"function(_) { return _.name; }\", rather than \"" + objectLiteralPropertyIdentifierStringContent + "\"");
                }

                // If the target is an [ObjectLiteral] then just set the property name on the target, don't try to call a setter (since it won't be defined)
                var objectLiteralPropertyName = objectLiteralPropertyNameMatchResults[objectLiteralPropertyNameMatchResults.Length - 1];
                return((target, newValue, ignoreAnyExistingLock) =>
                {
                    Script.Write("target[{0}] = {1};", objectLiteralPropertyName, newValue);
                });
            }

            // Note: A property specified directly on the target type will be specified using the simple format "function (_) { return _.Name; }" (before Bridge 16, there
            // were custom getter and setter functions - eg. "function (_) { return _.getName(); }" - but now it uses ES5 properties and so the simpler version appears in
            // the generated JavaScript. If the target property is specified via an interface cast then an alias property name will be used (in case there is a property on
            // the target type with the same name as the interface property and then the interface property is implemented explicitly) - it will look something like
            // "function (_) { return _.Example$IHaveName$Name; }" and we need to correctly handle that below too.
            var regExSegments = new[] {
                AsRegExSegment(string.Format(
                                   "function ({0}) {{",
                                   argumentName
                                   )),
                AsRegExSegment(string.Format(
                                   "return {0}.",
                                   argumentName
                                   )),
                AsRegExSegment("; }")
            };
            var expectedFunctionFormatMatcher = new JsRegex(
                string.Join("([.\\s\\S]*?)", regExSegments)
                );
            var propertyIdentifierStringContent = GetNormalisedFunctionStringRepresentation(propertyIdentifier);
            var propertyNameMatchResults        = expectedFunctionFormatMatcher.Exec(propertyIdentifierStringContent);

            if (propertyNameMatchResults == null)
            {
                throw new ArgumentException("The specified propertyIdentifier function did not match the expected format - must be a simple property get, such as \"function(_) { return _.Name; }\", rather than \"" + propertyIdentifierStringContent + "\"");
            }

            var typeAliasPrefix = propertyNameMatchResults[propertyNameMatchResults.Length - 2];;
            var propertyName    = propertyNameMatchResults[propertyNameMatchResults.Length - 1];

            var propertyDescriptorIfDefined = TryToGetPropertyDescriptor(source, propertyName);

            if (propertyDescriptorIfDefined == null)
            {
                throw new ArgumentException("Failed to find expected property \"" + propertyName + "\" (could not retrieve PropertyDescriptor)");
            }

            // 2018-01-18 DWR: Bridge 16.0 moved to auto properties being ES6 style properties with getters and setters at all times but 16.3 introduced an option for them to be "Plain" rather than "Managed"
            // and the bridge.json that is included with the Bridge NuGet package enables this option and so we can't presume that a setter will be present. If not, check whether the property is declared as
            // being "Writable" and generate a setter function that will work as if the "Managed" autoProperty rule is specified.
            var setter = propertyDescriptorIfDefined.OptionalSetter;

            if (Script.Write <bool>("!!setter"))
            {
                var hasExpectedSetter = Script.Write <bool>("(typeof(setter) === \"function\") && (setter.length === 1)");
                if (!hasExpectedSetter)
                {
                    throw new ArgumentException("Property setter does not match expected format (single argument function) for \"" + propertyName + "\"");
                }
            }
            else if (propertyDescriptorIfDefined.IsWritableIfPlainAutoProperty)
            {
                setter = Script.Write <Action <object> >("function (value) { this[{0}] = value; }", propertyName);
            }
            else
            {
                throw new ArgumentException("Failed to retrieve expected property setter for \"" + propertyName + "\"");
            }

            return((target, newValue, ignoreAnyExistingLock) =>
            {
                var isLocked = false;
                var propertyLockName = "__" + propertyName + "_Lock";

                /*@isLocked = !ignoreAnyExistingLock && (target[propertyLockName] === true);
                 *      if (!isLocked) {
                 *              setter.apply(target, [newValue]);
                 *              target[propertyLockName] = true;
                 *      }*/
                if (isLocked)
                {
                    throw new ArgumentException("This property has been locked - it should only be set within the constructor");
                }
            });
        }