@@ -2,7 +2,7 @@ const path = require('path')
22const slash = require ( 'slash' )
33const fs = require ( 'fs' )
44const walk = require ( 'walk-sync' )
5- const { zip } = require ( 'lodash' )
5+ const { zip, groupBy } = require ( 'lodash' )
66const yaml = require ( 'js-yaml' )
77const revalidator = require ( 'revalidator' )
88const generateMarkdownAST = require ( 'mdast-util-from-markdown' )
@@ -12,12 +12,14 @@ const languages = require('../../lib/languages')
1212const { tags } = require ( '../../lib/liquid-tags/extended-markdown' )
1313const ghesReleaseNotesSchema = require ( '../../lib/release-notes-schema' )
1414const renderContent = require ( '../../lib/render-content' )
15+ const { execSync } = require ( 'child_process' )
1516
1617const rootDir = path . join ( __dirname , '../..' )
1718const contentDir = path . join ( rootDir , 'content' )
1819const reusablesDir = path . join ( rootDir , 'data/reusables' )
1920const variablesDir = path . join ( rootDir , 'data/variables' )
2021const glossariesDir = path . join ( rootDir , 'data/glossaries' )
22+ const ghesReleaseNotesDir = path . join ( rootDir , 'data/release-notes' )
2123
2224const languageCodes = Object . keys ( languages )
2325
@@ -149,13 +151,26 @@ const oldVariableErrorText = 'Found article uses old {{ site.data... }} syntax.
149151const oldOcticonErrorText = 'Found octicon variables with the old {{ octicon-name }} syntax. Use {% octicon "name" %} instead!'
150152const oldExtendedMarkdownErrorText = 'Found extended markdown tags with the old {{#note}} syntax. Use {% note %}/{% endnote %} instead!'
151153
152- describe ( 'lint-files' , ( ) => {
153- const mdWalkOptions = {
154- globs : [ '**/*.md' ] ,
155- ignore : [ '**/README.md' ] ,
156- directories : false ,
157- includeBasePath : true
158- }
154+ const mdWalkOptions = {
155+ globs : [ '**/*.md' ] ,
156+ ignore : [ '**/README.md' ] ,
157+ directories : false ,
158+ includeBasePath : true
159+ }
160+
161+ // Also test the "data/variables/" YAML files
162+
163+ const yamlWalkOptions = {
164+ globs : [ '**/*.yml' ] ,
165+ directories : false ,
166+ includeBasePath : true
167+ }
168+
169+ // different lint rules apply to different content types
170+ let mdToLint , ymlToLint , releaseNotesToLint
171+
172+ if ( ! process . env . TEST_TRANSLATION ) {
173+ // compile lists of all the files we want to lint
159174
160175 const contentMarkdownAbsPaths = walk ( contentDir , mdWalkOptions ) . sort ( )
161176 const contentMarkdownRelPaths = contentMarkdownAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
@@ -165,16 +180,81 @@ describe('lint-files', () => {
165180 const reusableMarkdownRelPaths = reusableMarkdownAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
166181 const reusableMarkdownTuples = zip ( reusableMarkdownRelPaths , reusableMarkdownAbsPaths )
167182
168- describe . each ( [ ...contentMarkdownTuples , ...reusableMarkdownTuples ] ) (
169- 'in "%s"' ,
183+ mdToLint = [ ...contentMarkdownTuples , ...reusableMarkdownTuples ]
184+
185+ // data/variables
186+ const variableYamlAbsPaths = walk ( variablesDir , yamlWalkOptions ) . sort ( )
187+ const variableYamlRelPaths = variableYamlAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
188+ const variableYamlTuples = zip ( variableYamlRelPaths , variableYamlAbsPaths )
189+
190+ // data/glossaries
191+ const glossariesYamlAbsPaths = walk ( glossariesDir , yamlWalkOptions ) . sort ( )
192+ const glossariesYamlRelPaths = glossariesYamlAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
193+ const glossariesYamlTuples = zip ( glossariesYamlRelPaths , glossariesYamlAbsPaths )
194+
195+ ymlToLint = [ ...variableYamlTuples , ...glossariesYamlTuples ]
196+
197+ // GHES release notes
198+ const ghesReleaseNotesYamlAbsPaths = walk ( ghesReleaseNotesDir , yamlWalkOptions ) . sort ( )
199+ const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths . map ( p => path . relative ( rootDir , p ) )
200+ releaseNotesToLint = zip ( ghesReleaseNotesYamlRelPaths , ghesReleaseNotesYamlAbsPaths )
201+ } else {
202+ console . log ( 'testing translations.' )
203+
204+ // get all translated markdown or yaml files by comparing files changed to main branch
205+ const changedFilesRelPaths = execSync ( 'git diff --name-only origin/main | egrep "^translations/.*/.+.(yml|md)$"' ) . toString ( ) . split ( '\n' )
206+ console . log ( `Found ${ changedFilesRelPaths . length } translated files.` )
207+
208+ const { mdRelPaths, ymlRelPaths, releaseNotesRelPaths } = groupBy ( changedFilesRelPaths , ( path ) => {
209+ // separate the changed files to different groups
210+ if ( path . endsWith ( 'README.md' ) ) {
211+ return 'throwAway'
212+ } else if ( path . endsWith ( '.md' ) ) {
213+ return 'mdRelPaths'
214+ } else if ( path . match ( / \/ d a t a \/ ( v a r i a b l e s | g l o s s a r i e s ) \/ / i) ) {
215+ return 'ymlRelPaths'
216+ } else if ( path . match ( / \/ d a t a \/ r e l e a s e - n o t e s \/ / i) ) {
217+ return 'releaseNotesRelPaths'
218+ } else {
219+ // we aren't linting the rest
220+ return 'throwAway'
221+ }
222+ } )
223+
224+ const [ mdTuples , ymlTuples , releaseNotesTuples ] = [ mdRelPaths , ymlRelPaths , releaseNotesRelPaths ] . map ( relPaths => {
225+ const absPaths = relPaths . map ( p => path . join ( rootDir , p ) )
226+ return zip ( relPaths , absPaths )
227+ } )
228+
229+ mdToLint = mdTuples
230+ ymlToLint = ymlTuples
231+ releaseNotesToLint = releaseNotesTuples
232+ }
233+
234+ function formatLinkError ( message , links ) {
235+ return `${ message } \n - ${ links . join ( '\n - ' ) } `
236+ }
237+
238+ // Returns `content` if its a string, or `content.description` if it can.
239+ // Used for getting the nested `description` key in glossary files.
240+ function getContent ( content ) {
241+ if ( typeof content === 'string' ) return content
242+ if ( typeof content . description === 'string' ) return content . description
243+ return null
244+ }
245+
246+ describe ( 'lint markdown content' , ( ) => {
247+ describe . each ( mdToLint ) (
248+ '%s' ,
170249 ( markdownRelPath , markdownAbsPath ) => {
171- let content , ast , links , isHidden , isEarlyAccess , isSitePolicy
250+ let content , ast , links , isHidden , isEarlyAccess , isSitePolicy , frontmatterErrors
172251
173252 beforeAll ( async ( ) => {
174253 const fileContents = await fs . promises . readFile ( markdownAbsPath , 'utf8' )
175- const { data, content : bodyContent } = frontmatter ( fileContents )
254+ const { data, content : bodyContent , errors } = frontmatter ( fileContents )
176255
177256 content = bodyContent
257+ frontmatterErrors = errors
178258 ast = generateMarkdownAST ( content )
179259 isHidden = data . hidden === true
180260 isEarlyAccess = markdownRelPath . split ( '/' ) . includes ( 'early-access' )
@@ -307,34 +387,20 @@ describe('lint-files', () => {
307387 . resolves
308388 . toBeTruthy ( )
309389 } )
390+
391+ if ( ! markdownRelPath . includes ( 'data/reusables' ) ) {
392+ test ( 'contains valid frontmatter' , ( ) => {
393+ const errorMessage = frontmatterErrors . map ( error => `- [${ error . property } ]: ${ error . actual } , ${ error . message } ` ) . join ( '\n' )
394+ expect ( frontmatterErrors . length , errorMessage ) . toBe ( 0 )
395+ } )
396+ }
310397 }
311398 )
399+ } )
312400
313- // Also test the "data/variables/" YAML files
314- const yamlWalkOptions = {
315- globs : [ '**/*.yml' ] ,
316- directories : false ,
317- includeBasePath : true
318- }
319-
320- const variableYamlAbsPaths = walk ( variablesDir , yamlWalkOptions ) . sort ( )
321- const variableYamlRelPaths = variableYamlAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
322- const variableYamlTuples = zip ( variableYamlRelPaths , variableYamlAbsPaths )
323-
324- const glossariesYamlAbsPaths = walk ( glossariesDir , yamlWalkOptions ) . sort ( )
325- const glossariesYamlRelPaths = glossariesYamlAbsPaths . map ( p => slash ( path . relative ( rootDir , p ) ) )
326- const glossariesYamlTuples = zip ( glossariesYamlRelPaths , glossariesYamlAbsPaths )
327-
328- // Returns `content` if its a string, or `content.description` if it can.
329- // Used for getting the nested `description` key in glossary files.
330- function getContent ( content ) {
331- if ( typeof content === 'string' ) return content
332- if ( typeof content . description === 'string' ) return content . description
333- return null
334- }
335-
336- describe . each ( [ ...variableYamlTuples , ...glossariesYamlTuples ] ) (
337- 'in "%s"' ,
401+ describe ( 'lint yaml content' , ( ) => {
402+ describe . each ( ymlToLint ) (
403+ '%s' ,
338404 ( yamlRelPath , yamlAbsPath ) => {
339405 let dictionary , isEarlyAccess
340406
@@ -518,16 +584,12 @@ describe('lint-files', () => {
518584 } )
519585 }
520586 )
587+ } )
521588
522- // GHES release notes
523- const ghesReleaseNotesDir = path . join ( __dirname , '../../data/release-notes' )
524- const ghesReleaseNotesYamlAbsPaths = walk ( ghesReleaseNotesDir , yamlWalkOptions ) . sort ( )
525- const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths . map ( p => path . relative ( rootDir , p ) )
526- const ghesReleaseNotesYamlTuples = zip ( ghesReleaseNotesYamlRelPaths , ghesReleaseNotesYamlAbsPaths )
527-
528- if ( ghesReleaseNotesYamlTuples . length > 0 ) {
529- describe . each ( ghesReleaseNotesYamlTuples ) (
530- 'in "%s"' ,
589+ describe ( 'lint release notes' , ( ) => {
590+ if ( releaseNotesToLint . length > 0 ) {
591+ describe . each ( releaseNotesToLint ) (
592+ '%s' ,
531593 ( yamlRelPath , yamlAbsPath ) => {
532594 let dictionary
533595
@@ -538,14 +600,10 @@ describe('lint-files', () => {
538600
539601 it ( 'matches the schema' , ( ) => {
540602 const { errors } = revalidator . validate ( dictionary , ghesReleaseNotesSchema )
541- const errorMessage = errors . map ( error => `- [${ error . property } ]: ${ error . attribute } , ${ error . message } ` ) . join ( '\n' )
603+ const errorMessage = errors . map ( error => `- [${ error . property } ]: ${ error . actual } , ${ error . message } ` ) . join ( '\n' )
542604 expect ( errors . length , errorMessage ) . toBe ( 0 )
543605 } )
544606 }
545607 )
546608 }
547609} )
548-
549- function formatLinkError ( message , links ) {
550- return `${ message } \n - ${ links . join ( '\n - ' ) } `
551- }
0 commit comments