diff --git a/lib/helpers.js b/lib/helpers.js index 3d16000..98abefd 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -65,6 +65,24 @@ exports.checkPropertyName = function(name) { return true; }; +/** + * Validate remote method name. Allows `.` in method name for prototype + * @param {String} name The user input + * @returns {String|Boolean} + */ +exports.validateRemoteMethodName = function(name) { + if (name.match(/\./) && !name.match(/^prototype\.([^.]*)$/)) { + return 'Name cannot contain "." characters except' + + ' the dot in "prototype." prefix'; + } + var result = exports.validateOptionalName(name, /[\/@\s\+%:]/); + if (result !== true) return result; + if (RESERVED_PROPERTY_NAMES.indexOf(name) !== -1) { + return name + ' is a reserved keyword. Please use another name'; + } + return true; +}; + /** * Validate required name for properties, data sources, or connectors * Empty name could not pass @@ -84,9 +102,13 @@ exports.validateRequiredName = function (name) { * @param {String} name The user input * @returns {String|Boolean} */ -exports.validateOptionalName = function (name) { - if (name.match(/[\/@\s\+%:\.]/)) { - return 'Name cannot contain special characters (/@+%:. ): ' + name; +exports.validateOptionalName = function (name, unallowedCharacters) { + if (!unallowedCharacters) { + unallowedCharacters = /[\/@\s\+%:\.]/; + } + if (name.match(unallowedCharacters)) { + return 'Name cannot contain special characters ' + unallowedCharacters + + name; } if (name !== encodeURIComponent(name)) { return 'Name cannot contain special characters escaped by ' + diff --git a/remote-method/index.js b/remote-method/index.js index 69417eb..d6bc7d1 100644 --- a/remote-method/index.js +++ b/remote-method/index.js @@ -13,7 +13,7 @@ var actions = require('../lib/actions'); var helpers = require('../lib/helpers'); var validateRequiredName = helpers.validateRequiredName; var validateOptionalName = helpers.validateOptionalName; -var checkPropertyName = helpers.checkPropertyName; +var validateRemoteMethodName = helpers.validateRemoteMethodName; var typeChoices = helpers.getTypeChoices(); var ModelDefinition = require('loopback-workspace').models.ModelDefinition; @@ -96,14 +96,16 @@ module.exports = yeoman.generators.Base.extend({ message: 'Enter the remote method name:', required: true, default: name, - validate: checkPropertyName + validate: validateRemoteMethodName }, { name: 'isStatic', message: 'Is Static?', - required:true, + required: false, type: 'confirm', - default: true + default: function(answers) { + return !answers.methodName.match(/^prototype\.(.*)$/); + } }, { name: 'description', @@ -111,7 +113,10 @@ module.exports = yeoman.generators.Base.extend({ } ]; this.prompt(prompts, function(answers) { - this.methodName = answers.methodName; + var m = answers.methodName.match(/^prototype\.(.*)$/); + var isStatic = !m; + var baseName = isStatic ? answers.methodName : m[1]; + this.methodName = baseName; this.isStatic = answers.isStatic; this.description = answers.description; done(); diff --git a/test/helpers.test.js b/test/helpers.test.js index 458c5b6..894d19d 100644 --- a/test/helpers.test.js +++ b/test/helpers.test.js @@ -10,6 +10,7 @@ var validateAppName = helpers.validateAppName; var validateOptionalName = helpers.validateOptionalName; var validateRequiredName = helpers.validateRequiredName; var checkRelationName = helpers.checkRelationName; +var validateRemoteMethodName = helpers.validateRemoteMethodName; require('chai').should(); var expect = require('chai').expect; var promise = require('bluebird'); @@ -76,6 +77,36 @@ describe('helpers', function() { }); }); + // test validateRemoteMethodName() + describe('validateRemoteMethodName()', function() { + it('should accept good names', function() { + testValidationAcceptsValue(validateRemoteMethodName, 'prop'); + testValidationAcceptsValue(validateRemoteMethodName, 'prop1'); + testValidationAcceptsValue(validateRemoteMethodName, 'my_prop'); + testValidationAcceptsValue(validateRemoteMethodName, 'my-prop'); + testValidationAcceptsValue(validateRemoteMethodName, 'prototype.method'); + }); + + it('should report errors for a name containing special chars', function() { + testValidationRejectsValue(validateRemoteMethodName, 'my prop'); + testValidationRejectsValue(validateRemoteMethodName, 'my/prop'); + testValidationRejectsValue(validateRemoteMethodName, 'my@prop'); + testValidationRejectsValue(validateRemoteMethodName, 'my+prop'); + testValidationRejectsValue(validateRemoteMethodName, 'my%prop'); + testValidationRejectsValue(validateRemoteMethodName, 'my:prop'); + testValidationRejectsValue(validateRemoteMethodName, 'm.prop'); + }); + + it('should accept empty name', function() { + testValidationAcceptsValue(validateOptionalName, ''); + }); + + it('should not accept name with . after `prototype.`', function() { + testValidationRejectsValue(validateRemoteMethodName, + 'prototype.dotted.method'); + }); + }); + // test checkRelationName() describe('checkRelationName()', function() { var sampleModelDefinition = new ModelDefinition([ diff --git a/test/remote-method.test.js b/test/remote-method.test.js index 168d08c..a15df05 100644 --- a/test/remote-method.test.js +++ b/test/remote-method.test.js @@ -61,6 +61,33 @@ describe('loopback:remote-method generator', function() { }); }); + it('method name with `prototype.` should be removed', function(done) { + var methodGenerator = givenMethodGenerator(); + helpers.mockPrompt(methodGenerator, { + model: 'Car', + methodName: 'prototype.myRemote', + isStatic: 'false', + desription: 'This is my first remote method', + httpPath: '', + acceptsArg: '', + returnsArg: '' + }); + + methodGenerator.run(function() { + var definition = common.readJsonSync('common/models/car.json'); + var methods = definition.methods || {}; + expect(methods).to.have.property('myRemote'); + expect(methods).to.not.have.property('prototype.myRemote'); + expect(methods.myRemote).to.eql({ + isStatic: false, + accepts: [], + returns: [], + http: [] + }); + done(); + }); + }); + function givenMethodGenerator() { var name = 'loopback:remote-method'; var path = '../../remote-method';