diff --git a/aio/tools/transforms/angular-content-package/index.js b/aio/tools/transforms/angular-content-package/index.js index 5584a41828..1a4b57c8a0 100644 --- a/aio/tools/transforms/angular-content-package/index.js +++ b/aio/tools/transforms/angular-content-package/index.js @@ -20,14 +20,25 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) // Where do we get the source files? .config(function(readFilesProcessor, collectExamples) { - const gitignoreFile = fs.readFileSync(path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore'), 'utf8'); + const gitignoreFilePath = path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore'); + const gitignoreFile = fs.readFileSync(gitignoreFilePath, 'utf8'); const gitignore = ignore().add(gitignoreFile); const examplePaths = glob.sync('**/*', { cwd: GUIDE_EXAMPLES_PATH, dot: true, ignore: '**/node_modules/**', mark: true }) .filter(filePath => filePath !== '.gitignore') // we are not interested in the .gitignore file itself .filter(filePath => !/\/$/.test(filePath)); // this filter removes the folders, leaving only files - const filteredExamplePaths = gitignore.filter(examplePaths) // filter out files that match the .gitignore rules - .map(filePath => path.resolve(GUIDE_EXAMPLES_PATH, filePath)); // we need the full paths for the filereader + const ignoredExamplePaths = []; + const resolvedExamplePaths = []; + + examplePaths.forEach(filePath => { + // filter out files that match the .gitignore rules + if (gitignore.ignores(filePath)) { + ignoredExamplePaths.push(filePath); + } else { + // we need the full paths for the filereader + resolvedExamplePaths.push(path.resolve(GUIDE_EXAMPLES_PATH, filePath)); + } + }); readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([ { @@ -48,7 +59,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) }, { basePath: CONTENTS_PATH, - include: filteredExamplePaths, + include: resolvedExamplePaths, fileReader: 'exampleFileReader' }, { @@ -69,6 +80,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) ]); collectExamples.exampleFolders.push('examples'); + collectExamples.registerIgnoredExamples(ignoredExamplePaths, gitignoreFilePath); }) diff --git a/aio/tools/transforms/examples-package/processors/collect-examples.js b/aio/tools/transforms/examples-package/processors/collect-examples.js index a12782386b..5bb6a94c50 100644 --- a/aio/tools/transforms/examples-package/processors/collect-examples.js +++ b/aio/tools/transforms/examples-package/processors/collect-examples.js @@ -6,7 +6,26 @@ module.exports = function collectExamples(exampleMap, regionParser, log, createD $runAfter: ['files-read'], $runBefore: ['parsing-tags'], $validate: {exampleFolders: {presence: true}}, - $process: function(docs) { + exampleFolders: [], + ignoredExamples: {}, + /** + * Call this method to indicate to the processor that some files, that actually exist, + * have been filtered out from being processed. + * @param paths an array of relative paths to the examples that have been ignored. + * @param gitIgnorePath the path to the gitignore file that caused this example to be ignored. + */ + registerIgnoredExamples(paths, gitIgnorePath) { + paths.forEach(path => { this.ignoredExamples[path] = gitIgnorePath; }); + }, + /** + * Call this method to find out if an example was ignored. + * @param path a relative path to the example file to test for being ignored. + * @returns the path to the .gitignore file. + */ + isExampleIgnored(path) { + return this.ignoredExamples[path]; + }, + $process(docs) { const exampleFolders = this.exampleFolders; const regionDocs = []; docs = docs.filter((doc) => { diff --git a/aio/tools/transforms/examples-package/processors/collect-examples.spec.js b/aio/tools/transforms/examples-package/processors/collect-examples.spec.js index 52480e7760..583c3a1029 100644 --- a/aio/tools/transforms/examples-package/processors/collect-examples.spec.js +++ b/aio/tools/transforms/examples-package/processors/collect-examples.spec.js @@ -23,161 +23,175 @@ describe('collectExampleRegions processor', () => { processor.exampleFolders = ['examples-1', 'examples-2']; }); - it('should identify example files that are in the exampleFolders', () => { - const docs = [ - createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'), - createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'), - createDoc('E', 'other/b/c.js') - ]; + describe('$process', () => { - processor.$process(docs); + it('should identify example files that are in the exampleFolders', () => { + const docs = [ + createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'), + createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'), + createDoc('E', 'other/b/c.js') + ]; - expect(exampleMap['examples-1']['x/app.js']).toBeDefined(); - expect(exampleMap['examples-1']['y/index.html']).toBeDefined(); - expect(exampleMap['examples-2']['s/app.js']).toBeDefined(); - expect(exampleMap['examples-2']['t/style.css']).toBeDefined(); + processor.$process(docs); - expect(exampleMap['other']).toBeUndefined(); - }); + expect(exampleMap['examples-1']['x/app.js']).toBeDefined(); + expect(exampleMap['examples-1']['y/index.html']).toBeDefined(); + expect(exampleMap['examples-2']['s/app.js']).toBeDefined(); + expect(exampleMap['examples-2']['t/style.css']).toBeDefined(); - it('should remove example files from the docs collection', () => { - const docs = [ - createDoc('Example A', 'examples-1/x/app.js'), - createDoc('Example B', 'examples-1/y/index.html'), - createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), - createDoc('Example C', 'examples-2/s/app.js'), - createDoc('Other doc 2', 'other/b/c.js', 'content') - ]; + expect(exampleMap['other']).toBeUndefined(); + }); - const processedDocs = processor.$process(docs); + it('should remove example files from the docs collection', () => { + const docs = [ + createDoc('Example A', 'examples-1/x/app.js'), + createDoc('Example B', 'examples-1/y/index.html'), + createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), + createDoc('Example C', 'examples-2/s/app.js'), + createDoc('Other doc 2', 'other/b/c.js', 'content') + ]; - expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]); - }); + const processedDocs = processor.$process(docs); - it('should not remove docs from the docs collection that are not example files', () => { - const docs = [ - createDoc('Example A', 'examples-1/x/app.js'), - createDoc('Example B', 'examples-1/y/index.html'), - createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), - createDoc('Example C', 'examples-2/s/app.js'), - createDoc('Other doc 2', 'other/b/c.js', 'content') - ]; + expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]); + }); - const processedDocs = processor.$process(docs); + it('should not remove docs from the docs collection that are not example files', () => { + const docs = [ + createDoc('Example A', 'examples-1/x/app.js'), + createDoc('Example B', 'examples-1/y/index.html'), + createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), + createDoc('Example C', 'examples-2/s/app.js'), + createDoc('Other doc 2', 'other/b/c.js', 'content') + ]; - expect(processedDocs.filter(doc => doc.docType !== 'example-file')) - .toEqual(jasmine.objectContaining([ + const processedDocs = processor.$process(docs); + + expect(processedDocs.filter(doc => doc.docType !== 'example-file')) + .toEqual(jasmine.objectContaining([ + createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), + createDoc('Other doc 2', 'other/b/c.js', 'content') + ])); + }); + + it('should call `regionParser` from with the content and file extension of each example doc', + () => { + const docs = [ + createDoc('Example A', 'examples-1/x/app.js'), + createDoc('Example B', 'examples-1/y/index.html'), createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), + createDoc('Example C', 'examples-2/s/app.js'), createDoc('Other doc 2', 'other/b/c.js', 'content') - ])); - }); + ]; - it('should call `regionParser` from with the content and file extension of each example doc', - () => { - const docs = [ - createDoc('Example A', 'examples-1/x/app.js'), - createDoc('Example B', 'examples-1/y/index.html'), - createDoc('Other doc 1', 'examples-2/t/style.css', 'content'), - createDoc('Example C', 'examples-2/s/app.js'), - createDoc('Other doc 2', 'other/b/c.js', 'content') - ]; + processor.$process(docs); - processor.$process(docs); - - expect(regionParser).toHaveBeenCalledTimes(3); - expect(regionParser).toHaveBeenCalledWith('Example A', 'js'); - expect(regionParser).toHaveBeenCalledWith('Example B', 'html'); - expect(regionParser).toHaveBeenCalledWith('Example C', 'js'); - }); + expect(regionParser).toHaveBeenCalledTimes(3); + expect(regionParser).toHaveBeenCalledWith('Example A', 'js'); + expect(regionParser).toHaveBeenCalledWith('Example B', 'html'); + expect(regionParser).toHaveBeenCalledWith('Example C', 'js'); + }); - it('should attach parsed content as renderedContent to the example file docs', () => { - const docs = [ - createDoc('A', 'examples-1/x/app.js'), - createDoc('B', 'examples-1/y/index.html'), - createDoc('C', 'examples-2/s/app.js'), - createDoc('D', 'examples-2/t/style.css'), - ]; + it('should attach parsed content as renderedContent to the example file docs', () => { + const docs = [ + createDoc('A', 'examples-1/x/app.js'), + createDoc('B', 'examples-1/y/index.html'), + createDoc('C', 'examples-2/s/app.js'), + createDoc('D', 'examples-2/t/style.css'), + ]; - processor.$process(docs); + processor.$process(docs); - expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A'); - expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B'); - expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C'); - expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D'); + expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A'); + expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B'); + expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C'); + expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D'); - }); - - it('should create region docs for each region in the example file docs', () => { - const docs = [ - createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'), - createDoc('\nB', 'examples-1/y/index.html'), - createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'), - ]; - - const newDocs = processor.$process(docs); - - expect(newDocs.length).toEqual(3); - expect(newDocs).toEqual([ - jasmine.objectContaining({ - docType: 'example-region', - name: 'dummy', - id: 'examples-1/x/app.js#dummy', - contents: 'js' - }), - jasmine.objectContaining({ - docType: 'example-region', - name: 'dummy', - id: 'examples-1/y/index.html#dummy', - contents: 'html' - }), - jasmine.objectContaining({ - docType: 'example-region', - name: 'dummy', - id: 'examples-2/t/style.css#dummy', - contents: 'css' - }) - ]); - }); - - it('should attach region docs to the example file docs', () => { - const docs = [ - createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'), - createDoc('\nB', 'examples-1/y/index.html'), - createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'), - ]; - - processor.$process(docs); - - expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({ - dummy: { - docType: 'example-region', - path: 'examples-1/x/app.js', - name: 'dummy', - id: 'examples-1/x/app.js#dummy', - aliases: ['examples-1/x/app.js#dummy'], - contents: 'js' - } }); - expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({ - dummy: { - docType: 'example-region', - path: 'examples-1/y/index.html', - name: 'dummy', - id: 'examples-1/y/index.html#dummy', - aliases: ['examples-1/y/index.html#dummy'], - contents: 'html' - } + + it('should create region docs for each region in the example file docs', () => { + const docs = [ + createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'), + createDoc('\nB', 'examples-1/y/index.html'), + createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'), + ]; + + const newDocs = processor.$process(docs); + + expect(newDocs.length).toEqual(3); + expect(newDocs).toEqual([ + jasmine.objectContaining({ + docType: 'example-region', + name: 'dummy', + id: 'examples-1/x/app.js#dummy', + contents: 'js' + }), + jasmine.objectContaining({ + docType: 'example-region', + name: 'dummy', + id: 'examples-1/y/index.html#dummy', + contents: 'html' + }), + jasmine.objectContaining({ + docType: 'example-region', + name: 'dummy', + id: 'examples-2/t/style.css#dummy', + contents: 'css' + }) + ]); }); - expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({ - dummy: { - docType: 'example-region', - path: 'examples-2/t/style.css', - name: 'dummy', - id: 'examples-2/t/style.css#dummy', - aliases: ['examples-2/t/style.css#dummy'], - contents: 'css' - } + + it('should attach region docs to the example file docs', () => { + const docs = [ + createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'), + createDoc('\nB', 'examples-1/y/index.html'), + createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'), + ]; + + processor.$process(docs); + + expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({ + dummy: { + docType: 'example-region', + path: 'examples-1/x/app.js', + name: 'dummy', + id: 'examples-1/x/app.js#dummy', + aliases: ['examples-1/x/app.js#dummy'], + contents: 'js' + } + }); + expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({ + dummy: { + docType: 'example-region', + path: 'examples-1/y/index.html', + name: 'dummy', + id: 'examples-1/y/index.html#dummy', + aliases: ['examples-1/y/index.html#dummy'], + contents: 'html' + } + }); + expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({ + dummy: { + docType: 'example-region', + path: 'examples-2/t/style.css', + name: 'dummy', + id: 'examples-2/t/style.css#dummy', + aliases: ['examples-2/t/style.css#dummy'], + contents: 'css' + } + }); + }); + }); + + describe('filtered examples', () => { + it('should indicate if an example was filtered', () => { + processor.registerIgnoredExamples(['c/d/e', 'e/f/g'], 'path/to/gitignore'); + processor.registerIgnoredExamples(['x/y/z'], 'path/to/other/gitignore'); + expect(processor.isExampleIgnored('a/b/c')).toBeFalsy(); + expect(processor.isExampleIgnored('c/d/e')).toEqual('path/to/gitignore'); + expect(processor.isExampleIgnored('e/f/g')).toEqual('path/to/gitignore'); + expect(processor.isExampleIgnored('x/y/z')).toEqual('path/to/other/gitignore'); }); }); }); diff --git a/aio/tools/transforms/examples-package/services/getExampleRegion.js b/aio/tools/transforms/examples-package/services/getExampleRegion.js index 47565942e0..ebf09c241f 100644 --- a/aio/tools/transforms/examples-package/services/getExampleRegion.js +++ b/aio/tools/transforms/examples-package/services/getExampleRegion.js @@ -14,15 +14,22 @@ module.exports = function getExampleRegion(exampleMap, createDocMessage, collect // If still no file then we error if (!exampleFile) { - const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' + - 'Example files can be found in: ' + EXAMPLES_FOLDERS.join(', '); - throw new Error(message); + const gitIgnoreFile = collectExamples.isExampleIgnored(relativePath); + if( gitIgnoreFile) { + const message = createDocMessage('Ignored example file... relativePath: "' + relativePath + '"', doc) + '\n' + + 'This example file exists but has been ignored by a rule, in "' + gitIgnoreFile + '".'; + throw new Error(message); + } else { + const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' + + 'Example files can be found in the following relative paths: ' + EXAMPLES_FOLDERS.map(function(folder) { return '"' + folder + '"'; }).join(', '); + throw new Error(message); + } } var sourceCodeDoc = exampleFile.regions[regionName || '']; if (!sourceCodeDoc) { const message = createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc) + '\n' + - 'Regions available are:' + Object.keys[exampleFile.regions]; + 'Regions available are: ' + Object.keys(exampleFile.regions).map(function(region) { return '"' + region + '"'; }).join(', '); throw new Error(message); } diff --git a/aio/tools/transforms/examples-package/services/getExampleRegion.spec.js b/aio/tools/transforms/examples-package/services/getExampleRegion.spec.js index b07ba44d7a..ba42d11805 100644 --- a/aio/tools/transforms/examples-package/services/getExampleRegion.spec.js +++ b/aio/tools/transforms/examples-package/services/getExampleRegion.spec.js @@ -11,6 +11,7 @@ describe('getExampleRegion', () => { collectExamples = injector.get('collectExamples'); exampleMap = injector.get('exampleMap'); collectExamples.exampleFolders = ['examples']; + collectExamples.registerIgnoredExamples(['filtered/path'], 'some/gitignore'); exampleMap['examples'] = { 'test/url': { regions: { '': { renderedContent: 'whole file' }, @@ -27,12 +28,19 @@ describe('getExampleRegion', () => { expect(getExampleRegion({}, 'test/url', 'region-1')).toEqual('region 1 contents'); }); - it('should throw an error if an example doesn\'t exist', function() { - expect(function() { + it('should throw an error if an example doesn\'t exist', () => { + expect(() => { getExampleRegion({}, 'missing/file', 'region-1'); - }).toThrowError(); - expect(function() { + }).toThrowError('Missing example file... relativePath: "missing/file". - doc\nExample files can be found in the following relative paths: "examples"'); + expect(() => { getExampleRegion({}, 'test/url', 'missing-region'); - }).toThrowError(); + }).toThrowError('Missing example region... relativePath: "test/url", region: "missing-region". - doc\nRegions available are: "", "region-1"'); + }); + + it('should throw an error if an example has been filtered out', () => { + expect(() => { + getExampleRegion({}, 'filtered/path', 'any-region'); + }).toThrowError('Ignored example file... relativePath: "filtered/path" - doc\n' + + 'This example file exists but has been ignored by a rule, in "some/gitignore".'); }); });