Skip to content
This repository was archived by the owner on Jan 19, 2023. It is now read-only.

Commit fb97e67

Browse files
petebacondarwinthePunderWoman
authored andcommitted
build(docs-infra): fail if there are unused example regions (angular#40479)
We can define regions in our examples that can be referenced and rendered in guides as code snippets. It is quite hard to ensure that these regions are maintained correctly. One reason for this is it is hard to know whether a region is being used or not. This commit adds a new processor that checks for unused named regions in examples and fails if any are found. Fixes angular#19761 PR Close angular#40479
1 parent 5dd604a commit fb97e67

File tree

7 files changed

+140
-4
lines changed

7 files changed

+140
-4
lines changed

aio/tools/transforms/angular-base-package/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ module.exports = new Package('angular-base', [
5353
inlineTagProcessor.inlineTagDefinitions.push(require('./inline-tag-defs/custom-search-defs/'));
5454
})
5555

56-
.config(function(checkAnchorLinksProcessor) {
57-
// This is disabled here to prevent false negatives for the `docs-watch` task.
56+
.config(function(checkAnchorLinksProcessor, checkForUnusedExampleRegions) {
57+
// These are disabled here to prevent false negatives for the `docs-watch` task.
5858
// It is re-enabled in the main `angular.io-package`
5959
checkAnchorLinksProcessor.$enabled = false;
60+
checkForUnusedExampleRegions.$enabled = false;
6061
})
6162

6263
// Where do we get the source files?

aio/tools/transforms/angular.io-package/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPacka
2929
renderDocsProcessor.extraData.versionInfo = versionInfo;
3030
})
3131

32+
.config(function(checkForUnusedExampleRegions) {
33+
// Re-enable this processor that was disabled in the `angular-base` package to
34+
// avoid running it during `serve-and-sync` runs.
35+
checkForUnusedExampleRegions.$enabled = true;
36+
})
37+
3238
.config(function(checkAnchorLinksProcessor, linkInlineTagDef, renderExamples) {
3339

3440
// Fail the processing if there is an invalid link

aio/tools/transforms/examples-package/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports =
1313

1414
.processor(require('./processors/collect-examples'))
1515
.processor(require('./processors/render-examples'))
16+
.processor(require('./processors/check-for-unused-example-regions'))
1617

1718
.config(function(readFilesProcessor, exampleFileReader) {
1819
readFilesProcessor.fileReaders.push(exampleFileReader);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* A processor that will fail if there are named example regions that are not used in any docs.
11+
*
12+
* @param {*} exampleMap - contains all the regions extracted from example files.
13+
*/
14+
module.exports = function checkForUnusedExampleRegions(exampleMap) {
15+
return {
16+
$runAfter: ['renderExamples'],
17+
$runBefore: ['writing-files'],
18+
$process() {
19+
const unusedExampleRegions = [];
20+
for (const exampleFolder of Object.values(exampleMap)) {
21+
for (const exampleFile of Object.values(exampleFolder)) {
22+
for (const [regionName, region] of Object.entries(exampleFile.regions)) {
23+
if (regionName === '' || region.usedInDoc) continue;
24+
unusedExampleRegions.push(region);
25+
}
26+
}
27+
}
28+
29+
if (unusedExampleRegions.length > 0) {
30+
const message = (unusedExampleRegions.length === 1 ?
31+
'There is 1 unused example region:\n' :
32+
`There are ${unusedExampleRegions.length} unused example regions:\n`) +
33+
unusedExampleRegions.map(region => ` - ${region.id}`).join('\n');
34+
throw new Error(message);
35+
}
36+
},
37+
};
38+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
const testPackage = require('../../helpers/test-package');
9+
const Dgeni = require('dgeni');
10+
11+
describe('checkForUnusedExampleRegions', () => {
12+
var processor, exampleMap;
13+
14+
beforeEach(function() {
15+
const dgeni = new Dgeni([testPackage('examples-package', true)]);
16+
const injector = dgeni.configureInjector();
17+
exampleMap = injector.get('exampleMap');
18+
processor = injector.get('checkForUnusedExampleRegions');
19+
});
20+
21+
describe('$process()', () => {
22+
it('should error if there is a named example region which has not been used', () => {
23+
exampleMap['examples'] = {
24+
'some/file': {
25+
regions: {
26+
'': {id: 'some/file#'},
27+
'used': {id: 'some/file#used', usedInDoc: {}},
28+
'not-used': {id: 'some/file#not-used'},
29+
}
30+
}
31+
};
32+
expect(() => processor.$process())
33+
.toThrowError('There is 1 unused example region:\n - some/file#not-used');
34+
});
35+
36+
it('should not error if there are no example folders', () => {
37+
expect(() => processor.$process()).not.toThrowError();
38+
});
39+
40+
it('should not error if there are no example files', () => {
41+
exampleMap['examples'] = {};
42+
expect(() => processor.$process()).not.toThrowError();
43+
});
44+
45+
it('should not error if there are no example regions', () => {
46+
exampleMap['examples'] = {
47+
'some/file': {
48+
regions: {},
49+
},
50+
};
51+
expect(() => processor.$process()).not.toThrowError();
52+
});
53+
54+
it('should not error if there are no named example regions', () => {
55+
exampleMap['examples'] = {
56+
'some/file': {
57+
regions: {
58+
'': {id: 'some/file#'},
59+
},
60+
},
61+
};
62+
expect(() => processor.$process()).not.toThrowError();
63+
});
64+
65+
it('should not error if there are no unused named example regions', () => {
66+
exampleMap['examples'] = {
67+
'some/file': {
68+
regions: {
69+
'': {id: 'some/file#'},
70+
'used': {id: 'some/file#used', usedInDoc: {}},
71+
}
72+
}
73+
};
74+
expect(() => processor.$process()).not.toThrowError();
75+
});
76+
});
77+
});

aio/tools/transforms/examples-package/services/getExampleRegion.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = function getExampleRegion(exampleMap, createDocMessage, collectExamples) {
1+
module.exports = function getExampleRegion(exampleMap, createDocMessage, collectExamples, log) {
22
return function getExampleRegionImpl(doc, relativePath, regionName) {
33
const EXAMPLES_FOLDERS = collectExamples.exampleFolders;
44

@@ -29,10 +29,12 @@ module.exports = function getExampleRegion(exampleMap, createDocMessage, collect
2929
var sourceCodeDoc = exampleFile.regions[regionName || ''];
3030
if (!sourceCodeDoc) {
3131
const message = createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc) + '\n' +
32-
'Regions available are: ' + Object.keys(exampleFile.regions).map(function(region) { return '"' + region + '"'; }).join(', ');
32+
'Regions available are: ' + Object.keys(exampleFile.regions).map(function(region) { return '"' + region + '"'; }).join(', ');
3333
throw new Error(message);
3434
}
3535

36+
sourceCodeDoc.usedInDoc = doc;
37+
log.debug(`Example region ${sourceCodeDoc.id} used in ${doc.id}`);
3638
return sourceCodeDoc.renderedContent;
3739
};
3840
};

aio/tools/transforms/examples-package/services/getExampleRegion.spec.js

+11
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,15 @@ describe('getExampleRegion', () => {
4343
}).toThrowError('Ignored example file... relativePath: "filtered/path" - doc\n' +
4444
'This example file exists but has been ignored by a rule, in "some/gitignore".');
4545
});
46+
47+
it('should mark the example as having been "used"', () => {
48+
const doc1 = {};
49+
const doc2 = {};
50+
expect(exampleMap['examples']['test/url'].regions['region-1'].usedInDoc).toBeUndefined();
51+
getExampleRegion(doc1, 'test/url', 'region-1');
52+
expect(exampleMap['examples']['test/url'].regions['region-1'].usedInDoc).toBe(doc1);
53+
expect(exampleMap['examples']['test/url'].regions[''].usedInDoc).toBeUndefined();
54+
getExampleRegion(doc2, 'test/url');
55+
expect(exampleMap['examples']['test/url'].regions[''].usedInDoc).toBe(doc2);
56+
});
4657
});

0 commit comments

Comments
 (0)