| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  | /** | 
					
						
							|  |  |  |  |  * Copyright 2017 Google Inc. All rights reserved. | 
					
						
							|  |  |  |  |  * | 
					
						
							|  |  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  |  * | 
					
						
							|  |  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  |  * | 
					
						
							|  |  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | const Message = require('../Message'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 15:34:53 -07:00
										 |  |  |  | function ensureReleasedAPILinks(sources, libversion) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |   // Release version is everything that doesn't include "-".
 | 
					
						
							| 
									
										
										
										
											2020-02-05 12:04:33 -08:00
										 |  |  |  |   const apiLinkRegex = /https:\/\/github.com\/microsoft\/playwright\/blob\/v[^/]*\/docs\/api.md/ig; | 
					
						
							| 
									
										
										
										
											2020-02-13 18:26:38 -08:00
										 |  |  |  |   const lastReleasedAPI = `https://github.com/microsoft/playwright/blob/v${libversion.split('-')[0]}/docs/api.md`; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const messages = []; | 
					
						
							|  |  |  |  |   for (const source of sources) { | 
					
						
							|  |  |  |  |     const text = source.text(); | 
					
						
							|  |  |  |  |     const newText = text.replace(apiLinkRegex, lastReleasedAPI); | 
					
						
							|  |  |  |  |     if (source.setText(newText)) | 
					
						
							|  |  |  |  |       messages.push(Message.warning(`GEN: updated ${source.projectPath()}`)); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return messages; | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 15:34:53 -07:00
										 |  |  |  | function runCommands(sources, {libversion, chromiumVersion, firefoxVersion}) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |   // Release version is everything that doesn't include "-".
 | 
					
						
							| 
									
										
										
										
											2020-02-13 18:26:38 -08:00
										 |  |  |  |   const isReleaseVersion = !libversion.includes('-'); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const messages = []; | 
					
						
							|  |  |  |  |   const commands = []; | 
					
						
							|  |  |  |  |   for (const source of sources) { | 
					
						
							|  |  |  |  |     const text = source.text(); | 
					
						
							|  |  |  |  |     const commandStartRegex = /<!--\s*gen:([a-z-]+)\s*-->/ig; | 
					
						
							|  |  |  |  |     const commandEndRegex = /<!--\s*gen:stop\s*-->/ig; | 
					
						
							|  |  |  |  |     let start; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     while (start = commandStartRegex.exec(text)) { // eslint-disable-line no-cond-assign
 | 
					
						
							|  |  |  |  |       commandEndRegex.lastIndex = commandStartRegex.lastIndex; | 
					
						
							|  |  |  |  |       const end = commandEndRegex.exec(text); | 
					
						
							|  |  |  |  |       if (!end) { | 
					
						
							|  |  |  |  |         messages.push(Message.error(`Failed to find 'gen:stop' for command ${start[0]}`)); | 
					
						
							|  |  |  |  |         return messages; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       const name = start[1]; | 
					
						
							|  |  |  |  |       const from = commandStartRegex.lastIndex; | 
					
						
							|  |  |  |  |       const to = end.index; | 
					
						
							|  |  |  |  |       const originalText = text.substring(from, to); | 
					
						
							|  |  |  |  |       commands.push({name, from, to, originalText, source}); | 
					
						
							|  |  |  |  |       commandStartRegex.lastIndex = commandEndRegex.lastIndex; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const changedSources = new Set(); | 
					
						
							|  |  |  |  |   // Iterate commands in reverse order so that edits don't conflict.
 | 
					
						
							|  |  |  |  |   commands.sort((a, b) => b.from - a.from); | 
					
						
							|  |  |  |  |   for (const command of commands) { | 
					
						
							|  |  |  |  |     let newText = null; | 
					
						
							|  |  |  |  |     if (command.name === 'version') | 
					
						
							| 
									
										
										
										
											2020-02-13 18:26:38 -08:00
										 |  |  |  |       newText = isReleaseVersion ? 'v' + libversion : 'Tip-Of-Tree'; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     else if (command.name === 'empty-if-release') | 
					
						
							|  |  |  |  |       newText = isReleaseVersion ? '' : command.originalText; | 
					
						
							| 
									
										
										
										
											2020-02-13 18:26:38 -08:00
										 |  |  |  |     else if (command.name === 'chromium-version-if-release') | 
					
						
							|  |  |  |  |       newText = isReleaseVersion ? chromiumVersion : command.originalText; | 
					
						
							|  |  |  |  |     else if (command.name === 'firefox-version-if-release') | 
					
						
							|  |  |  |  |       newText = isReleaseVersion ? firefoxVersion : command.originalText; | 
					
						
							|  |  |  |  |     else if (command.name === 'chromium-version-badge-if-release') | 
					
						
							|  |  |  |  |       newText = isReleaseVersion ? `[](https://www.chromium.org/Home)` : command.originalText; | 
					
						
							|  |  |  |  |     else if (command.name === 'firefox-version-badge-if-release') | 
					
						
							|  |  |  |  |       newText = isReleaseVersion ? `[](https://www.mozilla.org/en-US/firefox/new/)` : command.originalText; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     else if (command.name === 'toc') | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |       newText = generateTableOfContents(command.source.text(), command.to, false /* topLevelOnly */); | 
					
						
							|  |  |  |  |     else if (command.name === 'toc-top-level') | 
					
						
							|  |  |  |  |       newText = generateTableOfContents(command.source.text(), command.to, true /* topLevelOnly */); | 
					
						
							| 
									
										
										
										
											2020-01-27 10:05:04 -08:00
										 |  |  |  |     else if (command.name.startsWith('toc-extends-')) | 
					
						
							|  |  |  |  |       newText = generateTableOfContentsForSuperclass(command.source.text(), 'class: ' + command.name.substring('toc-extends-'.length)); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     if (newText === null) | 
					
						
							|  |  |  |  |       messages.push(Message.error(`Unknown command 'gen:${command.name}'`)); | 
					
						
							|  |  |  |  |     else if (applyCommand(command, newText)) | 
					
						
							|  |  |  |  |       changedSources.add(command.source); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   for (const source of changedSources) | 
					
						
							|  |  |  |  |     messages.push(Message.warning(`GEN: updated ${source.projectPath()}`)); | 
					
						
							|  |  |  |  |   return messages; | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /** | 
					
						
							|  |  |  |  |  * @param {{name: string, from: number, to: number, source: !Source}} command | 
					
						
							|  |  |  |  |  * @param {string} editText | 
					
						
							|  |  |  |  |  * @return {boolean} | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | function applyCommand(command, editText) { | 
					
						
							|  |  |  |  |   const text = command.source.text(); | 
					
						
							|  |  |  |  |   const newText = text.substring(0, command.from) + editText + text.substring(command.to); | 
					
						
							|  |  |  |  |   return command.source.setText(newText); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  | function getTOCEntriesForText(text) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |   const ids = new Set(); | 
					
						
							|  |  |  |  |   const titles = []; | 
					
						
							|  |  |  |  |   let insideCodeBlock = false; | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |   let offset = 0; | 
					
						
							|  |  |  |  |   text.split('\n').forEach((aLine, lineNumber) => { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     const line = aLine.trim(); | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |     if (line.startsWith('```')) | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |       insideCodeBlock = !insideCodeBlock; | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |     else if (!insideCodeBlock && line.startsWith('#')) | 
					
						
							|  |  |  |  |       titles.push({line, offset: offset + lineNumber}); | 
					
						
							|  |  |  |  |     offset += aLine.length; | 
					
						
							|  |  |  |  |   }); | 
					
						
							|  |  |  |  |   let tocEntries = []; | 
					
						
							|  |  |  |  |   for (const {line, offset} of titles) { | 
					
						
							|  |  |  |  |     const [, nesting, name] = line.match(/^(#+)\s+(.*)$/); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     const delinkifiedName = name.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1'); | 
					
						
							|  |  |  |  |     const id = delinkifiedName.trim().toLowerCase().replace(/\s/g, '-').replace(/[^-0-9a-zа-яё]/ig, ''); | 
					
						
							|  |  |  |  |     let dedupId = id; | 
					
						
							|  |  |  |  |     let counter = 0; | 
					
						
							|  |  |  |  |     while (ids.has(dedupId)) | 
					
						
							|  |  |  |  |       dedupId = id + '-' + (++counter); | 
					
						
							|  |  |  |  |     ids.add(dedupId); | 
					
						
							|  |  |  |  |     tocEntries.push({ | 
					
						
							|  |  |  |  |       level: nesting.length, | 
					
						
							|  |  |  |  |       name: delinkifiedName, | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |       id: dedupId, | 
					
						
							|  |  |  |  |       offset, | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |   return tocEntries; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 15:34:53 -07:00
										 |  |  |  | /** | 
					
						
							|  |  |  |  |  * @param {string} text | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | function ensureInternalLinksAreValid(sources) { | 
					
						
							|  |  |  |  |   const messages = []; | 
					
						
							|  |  |  |  |   for (const source of sources) { | 
					
						
							|  |  |  |  |     const text = source.text(); | 
					
						
							|  |  |  |  |     const availableLinks = new Set(getTOCEntriesForText(text).map(entry => entry.id)); | 
					
						
							|  |  |  |  |     const internalLinkRegex = /\]\(#([#\w\-]*)\)/g; | 
					
						
							|  |  |  |  |     let match; | 
					
						
							|  |  |  |  |     while ((match = internalLinkRegex.exec(text)) !== null) { | 
					
						
							|  |  |  |  |       const link = match[1]; | 
					
						
							|  |  |  |  |       if (!availableLinks.has(link)) | 
					
						
							|  |  |  |  |         messages.push(Message.error(`Found invalid link: #${match[1]}`)); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return messages; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  | function generateTableOfContents(text, offset, topLevelOnly) { | 
					
						
							|  |  |  |  |   const allTocEntries = getTOCEntriesForText(text); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   let tocEntries = []; | 
					
						
							|  |  |  |  |   let nesting = 0; | 
					
						
							|  |  |  |  |   for (const tocEntry of allTocEntries) { | 
					
						
							|  |  |  |  |     if (tocEntry.offset < offset) | 
					
						
							|  |  |  |  |       continue; | 
					
						
							|  |  |  |  |     if (tocEntries.length) { | 
					
						
							|  |  |  |  |       nesting += tocEntry.level - tocEntries[tocEntries.length - 1].level; | 
					
						
							|  |  |  |  |       if (nesting < 0) | 
					
						
							|  |  |  |  |         break; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     tocEntries.push(tocEntry); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const minLevel = Math.min(...tocEntries.map(entry => entry.level)); | 
					
						
							|  |  |  |  |   tocEntries.forEach(entry => entry.level -= minLevel); | 
					
						
							| 
									
										
										
										
											2020-01-22 12:21:45 -08:00
										 |  |  |  |   if (topLevelOnly) | 
					
						
							|  |  |  |  |     tocEntries = tocEntries.filter(entry => !entry.level); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  |   return '\n' + tocEntries.map(entry => { | 
					
						
							|  |  |  |  |     const prefix = entry.level % 2 === 0 ? '-' : '*'; | 
					
						
							|  |  |  |  |     const padding = '  '.repeat(entry.level); | 
					
						
							|  |  |  |  |     return `${padding}${prefix} [${entry.name}](#${entry.id})`; | 
					
						
							|  |  |  |  |   }).join('\n') + '\n'; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-01-27 10:05:04 -08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | function generateTableOfContentsForSuperclass(text, name) { | 
					
						
							|  |  |  |  |   const allTocEntries = getTOCEntriesForText(text); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   for (const tocEntry of allTocEntries) { | 
					
						
							|  |  |  |  |     if (tocEntry.name !== name) | 
					
						
							|  |  |  |  |       continue; | 
					
						
							|  |  |  |  |     const offset = text.indexOf('<!-- GEN:stop -->', tocEntry.offset); | 
					
						
							|  |  |  |  |     return generateTableOfContents(text, offset, false); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return text; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-03-18 15:34:53 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | module.exports = {ensureInternalLinksAreValid, runCommands, ensureReleasedAPILinks}; |