fix(compiler): Metadata should not include methods on Object.prototype (#38292)
This commit fixes a bug in View Engine whereby the compiler errorneously
thinks that a method of a component has decorator metadata when that
method is one of those in `Object.prototype`, for example `toString`.
This bug is discovered in v10.0.4 of `@angular/language-service` after
the default bundle format was switched from ES5 to ES2015.
ES5 output:
```js
if (propMetadata[propName]) {
    decorators.push.apply(decorators, __spread(propMetadata[propName]));
}
```
ES2015 output:
```js
if (propMetadata[propName]) {
    decorators.push(...propMetadata[propName]);
}
```
The bug was not discovered in ES5 because the polyfill for the spread
operator happily accepts parameters that do not have the `iterable`
symbol:
```js
function __spread() {
    for (var ar = [], i = 0; i < arguments.length; i++)
        ar = ar.concat(__read(arguments[i]));
    return ar;
}
```
whereas in es2015 it’ll fail since the iterable symbol is not present in
`propMetadata['toString']` which evaluates to a function.
Fixes https://github.com/angular/vscode-ng-language-service/issues/859
PR Close #38292
			
			
This commit is contained in:
		
							parent
							
								
									3a525d196b
								
							
						
					
					
						commit
						fd51e01335
					
				| @ -235,7 +235,9 @@ export class StaticReflector implements CompileReflector { | ||||
|         const prop = (<any[]>propData) | ||||
|                          .find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method'); | ||||
|         const decorators: any[] = []; | ||||
|         if (propMetadata![propName]) { | ||||
|         // hasOwnProperty() is used here to make sure we do not look up methods
 | ||||
|         // on `Object.prototype`.
 | ||||
|         if (propMetadata?.hasOwnProperty(propName)) { | ||||
|           decorators.push(...propMetadata![propName]); | ||||
|         } | ||||
|         propMetadata![propName] = decorators; | ||||
|  | ||||
| @ -777,6 +777,35 @@ describe('StaticReflector', () => { | ||||
|           .toEqual({}); | ||||
|     }); | ||||
| 
 | ||||
|     it('should not inherit methods from Object.prototype', () => { | ||||
|       const filePath = '/tmp/test.ts'; | ||||
|       init({ | ||||
|         ...DEFAULT_TEST_DATA, | ||||
|         [filePath]: ` | ||||
|           import {Component} from '@angular/core'; | ||||
| 
 | ||||
|           @Component({ | ||||
|             selector: 'test-component', | ||||
|           }) | ||||
|           export class TestComponent { | ||||
|             title = 'Hello World'; | ||||
| 
 | ||||
|             toString() { | ||||
|               return 'Test Component'; | ||||
|             } | ||||
|           } | ||||
|         `,
 | ||||
|       }); | ||||
|       const declaration = reflector.getStaticSymbol(filePath, 'TestComponent'); | ||||
|       expect(declaration.filePath).toBe(filePath); | ||||
|       expect(declaration.name).toBe('TestComponent'); | ||||
|       const propMetadata = reflector.propMetadata(declaration); | ||||
|       // 'toString' is a member of TestComponent so it should be part of the metadata.
 | ||||
|       expect(propMetadata.hasOwnProperty('toString')).toBe(true); | ||||
|       // There are no decorators on 'toString' so it should be an empty array.
 | ||||
|       expect(propMetadata['toString']).toEqual([]); | ||||
|     }); | ||||
| 
 | ||||
|     it('should inherit lifecycle hooks', () => { | ||||
|       initWithDecorator({ | ||||
|         '/tmp/src/main.ts': ` | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user