build: fix symbol extractor not dealing with ES2015 classes (#38093)
We recently reworked our `ng_rollup_bundle` rule to no longer output
ESM5 and to optimize applications properly (previously applications were
not optimized properly due to incorrect build optimizer setup).
This change meant that a lot of symbols have been removed from the
golden correctly. See: fd65958b88
Unfortunately though, a few symbols have been accidentally removed
because they are now part of the bundle as ES2015 classes which the
symbol extractor does not pick up. This commit fixes the symbol
extractor to capture ES2015 classes. We also update the golden to
reflect this change.
PR Close #38093
This commit is contained in:
parent
cf9a47ba53
commit
e382632473
|
@ -35,6 +35,12 @@
|
||||||
{
|
{
|
||||||
"name": "NO_CHANGE"
|
"name": "NO_CHANGE"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NodeInjectorFactory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SimpleChange"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "TriggerComponent"
|
"name": "TriggerComponent"
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
{
|
{
|
||||||
"name": "NO_CHANGE"
|
"name": "NO_CHANGE"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NodeInjectorFactory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SimpleChange"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ViewEncapsulation"
|
"name": "ViewEncapsulation"
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
{
|
{
|
||||||
"name": "InjectFlags"
|
"name": "InjectFlags"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "InjectionToken"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NEW_LINE"
|
"name": "NEW_LINE"
|
||||||
},
|
},
|
||||||
|
@ -44,9 +47,15 @@
|
||||||
{
|
{
|
||||||
"name": "NULL_INJECTOR"
|
"name": "NULL_INJECTOR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NullInjector"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Optional"
|
"name": "Optional"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "R3Injector"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ScopedService"
|
"name": "ScopedService"
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
{
|
{
|
||||||
"name": "ChangeDetectionStrategy"
|
"name": "ChangeDetectionStrategy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "DefaultIterableDiffer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DefaultIterableDifferFactory"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "EMPTY_ARRAY"
|
"name": "EMPTY_ARRAY"
|
||||||
},
|
},
|
||||||
|
@ -17,9 +23,15 @@
|
||||||
{
|
{
|
||||||
"name": "ElementRef"
|
"name": "ElementRef"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ErrorHandler"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "InjectFlags"
|
"name": "InjectFlags"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "IterableChangeRecord_"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "IterableDiffers"
|
"name": "IterableDiffers"
|
||||||
},
|
},
|
||||||
|
@ -53,12 +65,30 @@
|
||||||
{
|
{
|
||||||
"name": "NgForOf"
|
"name": "NgForOf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NgForOfContext"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NgIf"
|
"name": "NgIf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NgIfContext"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NgModuleRef"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NodeInjector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NodeInjectorFactory"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Optional"
|
"name": "Optional"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "RecordViewTuple"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "SWITCH_ELEMENT_REF_FACTORY"
|
"name": "SWITCH_ELEMENT_REF_FACTORY"
|
||||||
},
|
},
|
||||||
|
@ -68,6 +98,9 @@
|
||||||
{
|
{
|
||||||
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SimpleChange"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "SkipSelf"
|
"name": "SkipSelf"
|
||||||
},
|
},
|
||||||
|
@ -92,6 +125,9 @@
|
||||||
{
|
{
|
||||||
"name": "ToDoAppComponent_section_5_li_3_input_6_Template"
|
"name": "ToDoAppComponent_section_5_li_3_input_6_Template"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Todo"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "TodoStore"
|
"name": "TodoStore"
|
||||||
},
|
},
|
||||||
|
@ -101,9 +137,18 @@
|
||||||
{
|
{
|
||||||
"name": "ViewEncapsulation"
|
"name": "ViewEncapsulation"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ViewRef"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "_CLEAN_PROMISE"
|
"name": "_CLEAN_PROMISE"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "_DuplicateItemRecordList"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_DuplicateMap"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "__forward_ref__"
|
"name": "__forward_ref__"
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,5 +37,7 @@ jasmine_node_test(
|
||||||
data = glob(["symbol_extractor_spec/**"]),
|
data = glob(["symbol_extractor_spec/**"]),
|
||||||
deps = [
|
deps = [
|
||||||
":test_lib",
|
":test_lib",
|
||||||
|
"//tools/symbol-extractor/symbol_extractor_spec:es2015_class_output",
|
||||||
|
"//tools/symbol-extractor/symbol_extractor_spec:fixtures",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -58,6 +58,10 @@ export class SymbolExtractor {
|
||||||
const funcDecl = child as ts.FunctionDeclaration;
|
const funcDecl = child as ts.FunctionDeclaration;
|
||||||
funcDecl.name && symbols.push({name: stripSuffix(funcDecl.name.getText())});
|
funcDecl.name && symbols.push({name: stripSuffix(funcDecl.name.getText())});
|
||||||
break;
|
break;
|
||||||
|
case ts.SyntaxKind.ClassDeclaration:
|
||||||
|
const classDecl = child as ts.ClassDeclaration;
|
||||||
|
classDecl.name && symbols.push({name: stripSuffix(classDecl.name.getText())});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Left for easier debugging.
|
// Left for easier debugging.
|
||||||
// console.log('###', ts.SyntaxKind[child.kind], child.getText());
|
// console.log('###', ts.SyntaxKind[child.kind], child.getText());
|
||||||
|
|
|
@ -8,31 +8,51 @@
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {Symbol, SymbolExtractor} from './symbol_extractor';
|
import {SymbolExtractor} from './symbol_extractor';
|
||||||
|
|
||||||
describe('scenarios', () => {
|
describe('scenarios', () => {
|
||||||
const symbolExtractorSpecDir = path.dirname(
|
const symbolExtractorSpecDir = path.dirname(
|
||||||
require.resolve('angular/tools/symbol-extractor/symbol_extractor_spec/empty.json'));
|
require.resolve('angular/tools/symbol-extractor/symbol_extractor_spec/empty.json'));
|
||||||
const scenarioFiles = fs.readdirSync(symbolExtractorSpecDir);
|
const scenarioFiles = fs.readdirSync(symbolExtractorSpecDir);
|
||||||
for (let i = 0; i < scenarioFiles.length; i = i + 2) {
|
for (let i = 0; i < scenarioFiles.length; i++) {
|
||||||
let jsFile = scenarioFiles[i];
|
const filePath = scenarioFiles[i];
|
||||||
let jsonFile = scenarioFiles[i + 1];
|
// We only consider files as tests if they have a `.js` extension, but do
|
||||||
let testName = jsFile.substring(0, jsFile.lastIndexOf('.'));
|
// not resolve to a tsickle externs file (which is a leftover from TS targets).
|
||||||
if (!jsFile.endsWith('.js')) throw new Error('Expected: .js file found: ' + jsFile);
|
if (!filePath.endsWith('.js') || filePath.endsWith('.externs.js')) {
|
||||||
if (!jsonFile.endsWith('.json')) throw new Error('Expected: .json file found: ' + jsonFile);
|
continue;
|
||||||
|
}
|
||||||
|
const testName = filePath.substring(0, filePath.lastIndexOf('.'));
|
||||||
|
const goldenFilePath = path.join(symbolExtractorSpecDir, `${testName}.json`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(goldenFilePath)) {
|
||||||
|
throw new Error(`No golden file found for test: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Left here so that it is easy to debug single test.
|
// Left here so that it is easy to debug single test.
|
||||||
// if (testName !== 'hello_world_min_debug') continue;
|
// if (testName !== 'hello_world_min_debug') continue;
|
||||||
|
|
||||||
it(testName, () => {
|
it(testName, () => {
|
||||||
const jsFileContent = fs.readFileSync(path.join(symbolExtractorSpecDir, jsFile)).toString();
|
const jsFileContent = fs.readFileSync(path.join(symbolExtractorSpecDir, filePath)).toString();
|
||||||
const jsonFileContent =
|
const jsonFileContent = fs.readFileSync(goldenFilePath).toString();
|
||||||
fs.readFileSync(path.join(symbolExtractorSpecDir, jsonFile)).toString();
|
|
||||||
const symbols = SymbolExtractor.parse(testName, jsFileContent);
|
const symbols = SymbolExtractor.parse(testName, jsFileContent);
|
||||||
const diff = SymbolExtractor.diff(symbols, jsonFileContent);
|
const diff = SymbolExtractor.diff(symbols, jsonFileContent);
|
||||||
expect(diff).toEqual({});
|
expect(diff).toEqual({});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests not existing in source root. We cannot glob for generated test fixtures as
|
||||||
|
// tests do not run in a sandbox on Windows.
|
||||||
|
|
||||||
|
it('should properly capture classes in TypeScript ES2015 class output', () => {
|
||||||
|
const jsFileContent = fs.readFileSync(
|
||||||
|
require.resolve(
|
||||||
|
'angular/tools/symbol-extractor/symbol_extractor_spec/es2015_class_output.mjs'),
|
||||||
|
'utf8');
|
||||||
|
const jsonFileContent =
|
||||||
|
fs.readFileSync(path.join(symbolExtractorSpecDir, 'es2015_class_output.json')).toString();
|
||||||
|
const symbols = SymbolExtractor.parse('es2015_class_output', jsFileContent);
|
||||||
|
const diff = SymbolExtractor.diff(symbols, jsonFileContent);
|
||||||
|
expect(diff).toEqual({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
load("//tools:defaults.bzl", "ts_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "es2015_class_output_lib",
|
||||||
|
srcs = ["es2015_class_output.ts"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "es2015_class_output",
|
||||||
|
srcs = [":es2015_class_output_lib"],
|
||||||
|
output_group = "es6_sources",
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "fixtures",
|
||||||
|
srcs = glob([
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.json",
|
||||||
|
]),
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
"HelloWorld",
|
||||||
|
"WithStaticMembers"
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HelloWorld {
|
||||||
|
greet() {
|
||||||
|
console.info('Hello!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript generates different output for classes with
|
||||||
|
// static members.
|
||||||
|
class WithStaticMembers extends HelloWorld {
|
||||||
|
static message = 'literal value';
|
||||||
|
}
|
Loading…
Reference in New Issue