Skip to content

Commit 33cc7ff

Browse files
committed
build: add CI check to ensure consistent exports for MDC packages
Adds a new script that checks whether an MDC package exports the same set of symbols as its non-MDC counterpart. Also fixes all of the failures.
1 parent d686290 commit 33cc7ff

File tree

16 files changed

+202
-12
lines changed

16 files changed

+202
-12
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ jobs:
348348
- run: yarn stylelint
349349
- run: yarn tslint
350350
- run: yarn -s ts-circular-deps:check
351+
- run: yarn check-mdc-exports
351352

352353
- *slack_notify_on_failure
353354
- *save_cache

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"integration-tests": "bazel test --test_tag_filters=-view-engine-only --build_tests_only -- //integration/... -//integration/size-test/...",
4747
"integration-tests:view-engine": "bazel test --test_tag_filters=view-engine-only --build_tests_only -- //integration/... -//integration/size-test/...",
4848
"integration-tests:size-test": "bazel test //integration/size-test/...",
49-
"check-mdc-tests": "ts-node --project scripts/tsconfig.json scripts/check-mdc-tests.ts"
49+
"check-mdc-tests": "ts-node --project scripts/tsconfig.json scripts/check-mdc-tests.ts",
50+
"check-mdc-exports": "ts-node --project scripts/tsconfig.json scripts/check-mdc-exports.ts"
5051
},
5152
"version": "11.1.0-next.0",
5253
"dependencies": {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export const config = {
2+
// The MDC sidenav hasn't been implemented yet.
3+
skippedPackages: ['mdc-sidenav'],
4+
skippedExports: {
5+
'chips': [
6+
// These components haven't been implemented for MDC due to a different accessibility pattern.
7+
'MatChipListChange',
8+
'MatChipList'
9+
],
10+
'autocomplete': [
11+
// Private base classes that are only exported for MDC.
12+
'_MatAutocompleteBase',
13+
// '_MatAutocompleteTriggerBase',
14+
'_MatAutocompleteOriginBase'
15+
],
16+
'core': [
17+
// Private base classes that are only exported for MDC.
18+
'_MatOptionBase',
19+
'_MatOptgroupBase'
20+
],
21+
'dialog': [
22+
// Private base classes and utility function that are only exported for MDC.
23+
'_MatDialogBase',
24+
'_MatDialogContainerBase',
25+
'_closeDialogVia',
26+
],
27+
'input': [
28+
// TODO: an MDC version of this directive has to be implemented.
29+
'MatTextareaAutosize'
30+
],
31+
'menu': [
32+
// Private base class that is only exported for MDC.
33+
'_MatMenuBase'
34+
],
35+
'paginator': [
36+
// Private base class that is only exported for MDC.
37+
'_MatPaginatorBase'
38+
],
39+
'radio': [
40+
// Private base classes that are only exported for MDC.
41+
'_MatRadioGroupBase',
42+
'_MatRadioButtonBase',
43+
],
44+
'select': [
45+
// Private base class that is only exported for MDC.
46+
'_MatSelectBase'
47+
],
48+
'slide-toggle': [
49+
// Private module used to provide some common functionality.
50+
'_MatSlideToggleRequiredValidatorModule'
51+
],
52+
'snack-bar': [
53+
// Private interface used to ensure consistency for MDC package.
54+
'_SnackBarContainer'
55+
],
56+
'tabs': [
57+
// Private base classes that are only exported for MDC.
58+
'_MatTabBodyBase',
59+
'_MatTabHeaderBase',
60+
'_MatTabNavBase',
61+
'_MatTabLinkBase',
62+
'_MatTabGroupBase'
63+
]
64+
}
65+
};

scripts/check-mdc-exports.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {join} from 'path';
2+
import {readdirSync, existsSync} from 'fs';
3+
import * as ts from 'typescript';
4+
import chalk from 'chalk';
5+
import {config} from './check-mdc-exports-config';
6+
7+
let hasFailed = false;
8+
9+
readdirSync(join(__dirname, '../src/material'), {withFileTypes: true})
10+
.filter(entity => entity.isDirectory())
11+
.map(entity => entity.name)
12+
.filter(name => !config.skippedPackages.includes(`mdc-${name}`))
13+
.filter(name => existsSync(join(__dirname, '../src/material-experimental', 'mdc-' + name)))
14+
.forEach(name => {
15+
const missingSymbols = getMissingSymbols(name, config.skippedExports[name] || []);
16+
17+
if (missingSymbols.length) {
18+
console.log(chalk.redBright(`\nMissing symbols from mdc-${name}:`));
19+
console.log(missingSymbols.join('\n'));
20+
hasFailed = true;
21+
}
22+
});
23+
24+
if (hasFailed) {
25+
console.log(chalk.redBright(
26+
'\nDetected one or more MDC packages do not export the same set of symbols from\n' +
27+
'public-api.ts as their non-MDC counterpart.\nEither implement the missing symbols or ' +
28+
're-export them from the Material package,\nor add them to the `skippedExports` list in ' +
29+
`scripts/check-mdc-exports-config.ts.`
30+
));
31+
process.exit(1);
32+
} else {
33+
console.log(chalk.green(
34+
'All MDC packages export the same public API symbols as their non-MDC counterparts.'));
35+
process.exit(0);
36+
}
37+
38+
/**
39+
* Gets the names of symbols that are present in a Material package,
40+
* but not its MDC counterpart.
41+
*/
42+
function getMissingSymbols(name: string, skipped: string[]): string[] {
43+
const mdcExports = getExports(`material-experimental/mdc-${name}`);
44+
const materialExports = getExports(`material/${name}`);
45+
46+
if (!mdcExports.length) {
47+
throw Error(`Could not resolve exports in mdc-${name}`);
48+
}
49+
50+
if (!materialExports.length) {
51+
throw Error(`Could not resolve exports in ${name}`);
52+
}
53+
54+
return materialExports.filter(exportName => {
55+
return !skipped.includes(exportName) && !mdcExports.includes(exportName);
56+
});
57+
}
58+
59+
/**
60+
* Gets the name of the exported symbols from a particular package.
61+
* Based on https://github.com/angular/angular/blob/master/tools/ts-api-guardian/lib/serializer.ts
62+
*/
63+
function getExports(name: string): string[] {
64+
const entryPoint = join(__dirname, '../src', name, 'public-api.ts');
65+
const program = ts.createProgram([entryPoint], {
66+
// This is a bit faster than the default and seems to produce identical results.
67+
moduleResolution: ts.ModuleResolutionKind.Classic
68+
});
69+
const sourceFile = program.getSourceFiles().find(f => f.fileName.endsWith('public-api.ts'))!;
70+
const typeChecker = program.getTypeChecker();
71+
const mainSymbol = typeChecker.getSymbolAtLocation(sourceFile);
72+
73+
return (mainSymbol ? (typeChecker.getExportsOfModule(mainSymbol) || []) : []).map(symbol => {
74+
// tslint:disable-next-line:no-bitwise
75+
if (symbol.flags & ts.SymbolFlags.Alias) {
76+
const resolvedSymbol = typeChecker.getAliasedSymbol(symbol);
77+
return (!resolvedSymbol.valueDeclaration && !resolvedSymbol.declarations) ?
78+
symbol : resolvedSymbol;
79+
} else {
80+
return symbol;
81+
}
82+
}).map(symbol => symbol.name);
83+
}

src/material-experimental/mdc-checkbox/public-api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {_MatCheckboxRequiredValidatorModule} from '@angular/material/checkbox';
10-
119
export * from './checkbox';
1210
export * from './module';
1311

@@ -21,4 +19,7 @@ export {
2119
* @breaking-change 9.0.0
2220
*/
2321
TransitionCheckState,
22+
MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY,
23+
MatCheckboxDefaultOptions,
24+
MAT_CHECKBOX_DEFAULT_OPTIONS,
2425
} from '@angular/material/checkbox';

src/material-experimental/mdc-dialog/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ export {
1919
throwMatDialogContentAlreadyAttachedError,
2020
DialogRole,
2121
DialogPosition,
22+
MAT_DIALOG_SCROLL_STRATEGY_FACTORY
2223
} from '@angular/material/dialog';

src/material-experimental/mdc-form-field/public-api.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
export {
10-
MAT_FORM_FIELD,
11-
MatFormFieldControl,
12-
getMatFormFieldDuplicatedHintError,
13-
getMatFormFieldMissingControlError,
14-
} from '@angular/material/form-field';
15-
169
export * from './directives/label';
1710
export * from './directives/error';
1811
export * from './directives/hint';
1912
export * from './directives/prefix';
2013
export * from './directives/suffix';
2114
export * from './form-field';
2215
export * from './module';
16+
17+
export {
18+
MAT_FORM_FIELD,
19+
MatFormFieldControl,
20+
getMatFormFieldDuplicatedHintError,
21+
getMatFormFieldMissingControlError,
22+
getMatFormFieldPlaceholderConflictError,
23+
_MAT_HINT,
24+
MatPlaceholder,
25+
matFormFieldAnimations,
26+
} from '@angular/material/form-field';

src/material-experimental/mdc-input/public-api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
export {getMatInputUnsupportedTypeError, MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
109
export {MatInput} from './input';
1110
export {MatInputModule} from './module';
11+
12+
export {
13+
getMatInputUnsupportedTypeError,
14+
MAT_INPUT_VALUE_ACCESSOR,
15+
} from '@angular/material/input';

src/material-experimental/mdc-list/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ ng_module(
2929
"//src/cdk/collections",
3030
"//src/material-experimental/mdc-core",
3131
"//src/material/divider",
32+
"//src/material/list",
3233
"@npm//@angular/core",
3334
"@npm//@angular/forms",
3435
"@npm//@material/list",

src/material-experimental/mdc-list/public-api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ export * from './selection-list';
1313
export * from './module';
1414
export * from './list-option';
1515
export * from './list-styling';
16+
17+
export {
18+
MAT_LIST,
19+
MAT_NAV_LIST,
20+
MAT_SELECTION_LIST_VALUE_ACCESSOR,
21+
} from '@angular/material/list';

0 commit comments

Comments
 (0)