forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build(aio): add checkContentRules processor (angular#22759)
This processor will enable us to write rules about how the content should appear, such as: * no headings in markdown content * only one sentence per line * no single character parameter names * etc. PR Close angular#22759
- Loading branch information
1 parent
e70076d
commit 61669f5
Showing
3 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
aio/tools/transforms/angular-base-package/processors/checkContentRules.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
|
||
/** | ||
* A processor that can run arbitrary checking rules against properties of documents | ||
* The configuration for the processor is via the `docTypeRules`. | ||
* This is a hash of docTypes to rulesets. | ||
* Each rules set is a hash of properties to rule functions. | ||
* | ||
* The processor will run each rule function against each matching property of each matching doc. | ||
* | ||
* An example rule might look like: | ||
* | ||
* ``` | ||
* function noMarkdownHeadings(doc, prop, value) { | ||
* const match = /^\s?#+\s+.*$/m.exec(value); | ||
* if (match) { | ||
* return `Headings not allowed in "${prop}" property. Found "${match[0]}"`; | ||
* } | ||
* } | ||
* ``` | ||
* | ||
*/ | ||
module.exports = function checkContentRules(log, createDocMessage) { | ||
return { | ||
/** | ||
* { | ||
* [docType]: { | ||
* [property]: Array<(doc: Document, property: string, value: any) => string|undefined> | ||
* } | ||
* } | ||
*/ | ||
docTypeRules: {}, | ||
failOnContentErrors: false, | ||
$runAfter: ['tags-extracted'], | ||
$runBefore: ['processing-docs'], | ||
$process(docs) { | ||
const errors = []; | ||
docs.forEach(doc => { | ||
const docErrors = []; | ||
const rules = this.docTypeRules[doc.docType] || {}; | ||
if (rules) { | ||
Object.keys(rules).forEach(property => { | ||
const ruleFns = rules[property]; | ||
ruleFns.forEach(ruleFn => { | ||
const error = ruleFn(doc, property, doc[property]); | ||
if (error) { | ||
docErrors.push(error); | ||
} | ||
}); | ||
}); | ||
} | ||
if (docErrors.length) { | ||
errors.push({ doc, errors: docErrors }); | ||
} | ||
}); | ||
|
||
if (errors.length) { | ||
log.error('Content contains errors'); | ||
errors.forEach(docError => { | ||
const errors = docError.errors.join('\n '); | ||
log.error(createDocMessage(errors + '\n ', docError.doc)); | ||
}); | ||
if (this.failOnContentErrors) { | ||
throw new Error('Stopping due to content errors.'); | ||
} | ||
} | ||
} | ||
}; | ||
}; |
108 changes: 108 additions & 0 deletions
108
aio/tools/transforms/angular-base-package/processors/checkContentRules.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
var testPackage = require('../../helpers/test-package'); | ||
var Dgeni = require('dgeni'); | ||
|
||
describe('checkContentRules processor', function() { | ||
let processor, logger; | ||
|
||
beforeEach(function() { | ||
const dgeni = new Dgeni([testPackage('angular-base-package')]); | ||
const injector = dgeni.configureInjector(); | ||
processor = injector.get('checkContentRules'); | ||
logger = injector.get('log'); | ||
}); | ||
|
||
it('should exist on the injector', () => { | ||
expect(processor).toBeDefined(); | ||
expect(processor.$process).toEqual(jasmine.any(Function)); | ||
}); | ||
|
||
it('shpuld run at the right time', () => { | ||
expect(processor.$runAfter).toEqual(['tags-extracted']); | ||
expect(processor.$runBefore).toEqual(['processing-docs']); | ||
}); | ||
|
||
it('should do nothing if not configured', () => { | ||
const docs = [{ docType: 'test', description: '## heading 2' }]; | ||
processor.$process(docs); | ||
expect(docs).toEqual([{ docType: 'test', description: '## heading 2' }]); | ||
|
||
expect(logger.error).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should run configured rules against matching docs', () => { | ||
const nameSpy1 = jasmine.createSpy('name 1'); | ||
const nameSpy2 = jasmine.createSpy('name 2'); | ||
const nameSpy3 = jasmine.createSpy('name 3'); | ||
const descriptionSpy1 = jasmine.createSpy('description 1'); | ||
const descriptionSpy2 = jasmine.createSpy('description 2'); | ||
const descriptionSpy3 = jasmine.createSpy('description 3'); | ||
|
||
processor.docTypeRules = { | ||
'test1': { | ||
name: [nameSpy1, nameSpy3], | ||
description: [descriptionSpy1, descriptionSpy3] | ||
}, | ||
'test2': { | ||
name: [nameSpy2], | ||
description: [descriptionSpy2] | ||
} | ||
}; | ||
|
||
const docs = [ | ||
{ docType: 'test1', description: 'test doc 1', name: 'test-1' }, | ||
{ docType: 'test2', description: 'test doc 2', name: 'test-2' } | ||
]; | ||
processor.$process(docs); | ||
expect(nameSpy1).toHaveBeenCalledTimes(1); | ||
expect(nameSpy1).toHaveBeenCalledWith(docs[0], 'name', 'test-1'); | ||
expect(nameSpy2).toHaveBeenCalledTimes(1); | ||
expect(nameSpy2).toHaveBeenCalledWith(docs[1], 'name', 'test-2'); | ||
expect(nameSpy3).toHaveBeenCalledTimes(1); | ||
expect(nameSpy3).toHaveBeenCalledWith(docs[0], 'name', 'test-1'); | ||
expect(descriptionSpy1).toHaveBeenCalledTimes(1); | ||
expect(descriptionSpy1).toHaveBeenCalledWith(docs[0], 'description', 'test doc 1'); | ||
expect(descriptionSpy2).toHaveBeenCalledTimes(1); | ||
expect(descriptionSpy2).toHaveBeenCalledWith(docs[1], 'description', 'test doc 2'); | ||
expect(descriptionSpy3).toHaveBeenCalledTimes(1); | ||
expect(descriptionSpy3).toHaveBeenCalledWith(docs[0], 'description', 'test doc 1'); | ||
}); | ||
|
||
it('should log errors if the rule returns error messages', () => { | ||
const nameSpy1 = jasmine.createSpy('name 1').and.returnValue('name error message'); | ||
const descriptionSpy1 = jasmine.createSpy('description 1').and.returnValue('description error message'); | ||
|
||
processor.docTypeRules = { | ||
'test1': { | ||
name: [nameSpy1], | ||
description: [descriptionSpy1] | ||
} | ||
}; | ||
|
||
const docs = [ | ||
{ docType: 'test1', description: 'test doc 1', name: 'test-1' }, | ||
{ docType: 'test2', description: 'test doc 2', name: 'test-2' } | ||
]; | ||
|
||
processor.$process(docs); | ||
|
||
expect(logger.error).toHaveBeenCalledTimes(2); | ||
expect(logger.error).toHaveBeenCalledWith('Content contains errors'); | ||
expect(logger.error).toHaveBeenCalledWith(`name error message | ||
description error message | ||
- doc "test-1" (test1) `); | ||
}); | ||
|
||
it('should throw an error if `failOnContentErrors` is true and errors are found', () => { | ||
const errorRule = jasmine.createSpy('error rule').and.returnValue('some error'); | ||
processor.docTypeRules = { | ||
'test': { description: [errorRule] } | ||
}; | ||
processor.failOnContentErrors = true; | ||
|
||
const docs = [ | ||
{ docType: 'test', description: 'test doc' }, | ||
]; | ||
expect(() => processor.$process(docs)).toThrowError('Stopping due to content errors.'); | ||
}); | ||
|
||
}); |