Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin issue. Help request #163

Closed
vatula opened this issue May 17, 2015 · 4 comments
Closed

Plugin issue. Help request #163

vatula opened this issue May 17, 2015 · 4 comments

Comments

@vatula
Copy link

vatula commented May 17, 2015

I have a provider written in ES6 way using classes, like this:

someModule.provider('es6Provider', class Es6Provider {

  $get(service1, service2) {
    // ...
  }
}

Which Babeljs transforms into something like this:

_someModule.provider('es6Provider', (function () {
  function Es6Provider () {
    _classCallCheck(this, Es6Provider );
  }
  Es6Provider.prototype.$get = function $get(service1, service2) {}
  return Es6Provider 
})());

And I am trying to write a plugin for ngAnnotate for our app that will annotate methods in the class. So suppose I have only that js file and I want to capture provider call.
What should I return from a match? :)
A match method of the plugin I wrote is similar to:

this.match = function(node) {
  var target = null;
  var isMethodCall = (
    node.type === "MemberExpression" &&
    node.computed === false
  ); // attempt to capture provider method
  if (isMethodCall) {
    var method = node.property;
    if (method.name === 'provider') {
      var args = node.$parent.arguments; // this will have an array of two arguments.
      // args[1].type === "CallExpression"
      // ^ that's Babel's anonymous function call (function() {...})()
    }
  }
}

I want to annotate all methods in a babelified es5, like these:

Es6Provider.prototype.$get

Returning args[1].callee does not help ngAnnotate to annotate it

Using prelude, comments etc are not welcomed since we want to use plugin in a non-intrusive, transparent way.

@olov
Copy link
Owner

olov commented May 20, 2015

Related to #111. Plugins need rework and documentation and I just don't have free bandwidth to do that so if you want to try them out then you're on your own basically. The starting point is to look at existing plugins (see angular-dashboard-framework.js in master) and match in ng-annotate-main.js (as well as its called functions).

I strongly recommend you to just do $get(service1, service2) { "ngInject"; ... }.

@olov olov closed this as completed May 20, 2015
@vatula
Copy link
Author

vatula commented May 20, 2015

@olov Unfortunately, in a real world, I cannot enforce new usage rules. Adding 'ngInject' by convention increases human error, since team already has developed conventions and is working like that for over a year. I am really grateful for low level API in ng-annotate since it allows adapt ng-annotate to project needs.

Any ways, I have wrote a plugin that works for our project, since we know the structure of our application and existing conventions.

It obviously is not generic. I simply mark all function expressions with angular-specific naming, since we don't use these names in our own classes. But again, I needed that for providers that were not captured.

module.exports = new function() {
  var context;
  this.init = function(ctx) {
    context = ctx;
  };

  this.match = function(node) {
    var target = null;
    var isMatch = (
      node.type === "FunctionExpression" &&
      node.id && node.id.name && (node.id.name[0] === '$')
    );

    if (isMatch) {
      target = node;
    }

    return target;
  }

};

@IS-Kuan
Copy link

IS-Kuan commented Feb 25, 2021

@vatula Curious why your code could work... I try to return the exactly the function node I want to be annotated, but output is not annotated at all...

class AppControllerProviderPlugin {
  constructor() {
    this.ctx = null
  }
  init (ctx) {
    this.ctx = ctx;
    // console.log('ctx.src===', ctx)
  }
  match (node) {
    // app.$controllerProvider.register("foo", function($scope) {});

    if (node.type !== 'FunctionExpression') { return false; }
    const parent1 = node.$parent
    if (parent1.type !== 'CallExpression') { return false; }  // means parent1 is a caller, so has callee.property.name
    if (parent1.callee.property.name !== 'register') { return false; }
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    console.log(node)
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    console.log('==============================================')
    return node
  }
}

And console.log seems correct (49~75 is exactly function ($rootScope) {})):

==============================================
==============================================
==============================================
==============================================
==============================================
Node {
  type: 'FunctionExpression',
  start: 49,
  end: 75,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 49 },
     end: Position { line: 3, column: 1 } },
  range: [ 49, 75 ],
  id: null,
  expression: false,
  generator: false,
  async: false,
  params:
   [ Node {
       type: 'Identifier',
       start: 59,
       end: 69,
       loc: [SourceLocation],
       range: [Array],
       name: '$rootScope',
       '$parent': [Circular],
       '$scope': [Scope] } ],
  body:
   Node {
     type: 'BlockStatement',
     start: 71,
     end: 75,
     loc: SourceLocation { start: [Position], end: [Position] },
     range: [ 71, 75 ],
     body: [],
     '$parent': [Circular],
     '$scope':
      Scope {
        kind: 'hoist',
        node: [Circular],
        parent: [Scope],
        children: [],
        decls: [Map],
        propagates: Set {} } },
  '$parent':
   Node {
     type: 'CallExpression',
     start: 0,
     end: 76,
     loc: SourceLocation { start: [Position], end: [Position] },
     range: [ 0, 76 ],
     callee:
      Node {
        type: 'MemberExpression',
        start: 0,
        end: 31,
        loc: [SourceLocation],
        range: [Array],
        object: [Node],
        property: [Node],
        computed: false,
        optional: false,
        '$parent': [Circular],
        '$scope': [Scope] },
     arguments: [ [Node], [Circular] ],
     optional: false,
     '$parent':
      Node {
        type: 'ExpressionStatement',
        start: 0,
        end: 76,
        loc: [SourceLocation],
        range: [Array],
        expression: [Circular],
        '$parent': [Node],
        '$scope': [Scope] },
     '$scope':
      Scope {
        kind: 'hoist',
        node: [Node],
        parent: [Scope],
        children: [Array],
        decls: Map {},
        propagates: Set {} } },
  '$scope':
   Scope {
     kind: 'hoist',
     node: [Circular],
     parent:
      Scope {
        kind: 'hoist',
        node: [Node],
        parent: [Scope],
        children: [Array],
        decls: Map {},
        propagates: Set {} },
     children: [],
     decls: Map { '$rootScope' => [Object] },
     propagates: Set {} } }
==============================================
==============================================
==============================================
==============================================
==============================================

And I'm sure built-in implicit matching is working:

/// INPUT FILE
var app = angular.module('webui')
app.controller('AAAAAAAAAAA', function ($scope) {})
app.controllerProvider.register('BBBBBBBBBBBB', function ($scope) {})

/// OUTPUT FILE
var app=angular.module("webui");
app.controller("AAAAAAAAAAA",["$scope",function($scope){}]);
app.controllerProvider.register("BBBBBBBBBBBB",function($scope){})

@IS-Kuan
Copy link

IS-Kuan commented Feb 25, 2021

Found the point, the reason is judgeSuspects() in ng-annotate-main.js:

    for (const target of finalSuspects) {
        if (target.$chained !== chainedRegular) {
            continue;
        }
   ...

And const chainedRegular = 4;, is a constant.

It seems to be used to precisely judge the correct context / scope of a FunctionExpression, but context doesn't that much matter in my using scenario so just add a workaround.

node.$chained = /* const chainedRegular = */ 4  // WORKAROUND, see ng-annotate-main.js

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

No branches or pull requests

3 participants