2

This doesn't work

interface String {
    contains(s:string):boolean;
}
String.prototype.contains=(s:string):boolean=>this.indexOf(s)!==-1;

because Property 'contains' does not exist on type 'String'

This is a bit of a surprise since adding it was the entire point of the interface declaration. http://www.typescriptlang.org/docs/handbook/declaration-merging.html suggests that the above code is legal. String is in the global namespace as far as I can tell by examining lib.es2015.wellknown.d.ts.

What's the right way to go about this? After reading Aluan Haddad's Extending third party module that is globally exposed I rewrote like this

declare global {
    interface String {
        contains(s: string): boolean;
    }
}
String.prototype.contains=(s:string):boolean=>this.indexOf(s)!==-1;

and the interface change is now correct. But now 'this' implicitly has type 'any' because it does not have a type annotation.

Per further comments this can be explicitly typed using function syntax.

String.prototype.contains = function (this: string, s:string):boolean { 
    return this.indexOf(s)!==-1; 
};

It should also be noted that in the course of this investigation I discovered that contains is implemented with the name includes and is declared in lib.es2015.core.d.ts

Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • Possibly not, perhaps Aurelia is interfering. That may be the problem. – Peter Wone Apr 26 '18 at 01:18
  • I covered the topic at length in this answer https://stackoverflow.com/a/43674912/1915893 – Aluan Haddad Apr 26 '18 at 01:19
  • It's not Aurelia it's the use of modules. If you are at module scope you need to wrap the interface declaration in order for it to be merged into the global array declaration. Otherwise you are defining a module scoped interface called Array. See the link to answer for full details and examples – Aluan Haddad Apr 26 '18 at 01:19
  • I see your edit but I don't understand what it has to do with something being at class or not. Just write it it doesn't have anything to do with classes in any way. – Aluan Haddad Apr 26 '18 at 01:43
  • In a class the type of `this` would be correct. – Peter Wone Apr 26 '18 at 01:48
  • 1
    It would be correct in a function as well just write `contains = function (this: string, s: string) {return this.indexOf(s)!==-1;}` and the wrapper needs to be an interface not a class because you can't merge a class. You also can't use an arrow function because this needs to be dynamic in a method in that's what your defining – Aluan Haddad Apr 26 '18 at 01:51
  • Well then, write it all up as an answer before we get a lecture on discussions in comments. I'm happy with the outcome. – Peter Wone Apr 26 '18 at 01:58
  • All right I did. I just thought the other answer covered it and I didn't want to spam. – Aluan Haddad Apr 26 '18 at 02:04
  • 1
    On a second read in the light of understanding I see that you did explain it in passing but I think it's a lot clearer in this answer. More importantly you have explained why it matters. – Peter Wone Apr 26 '18 at 02:07

1 Answers1

2

If you're defining the augmentation inside of a module, that is a file containing a top-level import or export then you need to use a declare global block in order to augment the global scope. Otherwise, the interface that you declare will not be merged into the global array interface because it's local to the module like any other declaration would be. The declare global syntax is specifically to cover this use case.

Furthermore, when you define the actual method you can't use an arrow function if the method itself is defined in terms of this because Arrow functions have a statically scope this while a dynamic this is needed for methods.

Putting it together

//  this is a module
export {}

declare global {
  interface String {
    contains(other: string): boolean;
  }
} 

String.prototype.contains = function (other) {
  return this.indexOf(other) and !== -1;
};

Note that whether the type being augmented is a class or an interface, the member needs to be declared in an interface as above because interfaces can merge with classes and interfaces can merge with interfaces but classes do not merge.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84