diff --git a/src/lib/study-refs.js b/src/lib/study-refs.js new file mode 100644 index 00000000..ab436c2e --- /dev/null +++ b/src/lib/study-refs.js @@ -0,0 +1,33 @@ +const { recordCategorizedAnomaly } = require('./util'); + +const possibleAnomalies = [ + 'discontinuedReferences' +]; + +function studyReferences (edResults) { + const report = []; + const recordAnomaly = recordCategorizedAnomaly(report, 'refs', possibleAnomalies); + edResults.forEach(spec => { + (spec.refs?.normative || []).forEach(ref => { + const referencedSpec = edResults.find(s => s.url === ref.url || s?.nightly.url === ref.url || s?.nightly?.alternateUrls?.includes(ref.url)); + + if (referencedSpec && referencedSpec.standing === "discontinued") { + + const newSpecsLinks = edResults.filter(s => referencedSpec.obsoletedBy?.includes(s.shortname)).map(s => `[${s.shortname}](${s?.nightly.url || s.url})`); + recordAnomaly(spec, 'discontinuedReferences', `[${ref.name}](${ref.url}) ${newSpecsLinks.length ? `has been obsoleted by ${newSpecsLinks}` : `is discontinued, no known replacement reference`}`); + } + }); + }); + return report; +} + +module.exports = { studyReferences }; + +if (require.main === module) { + (async function() { + const { loadCrawlResults } = require('../lib/util'); + const crawl = await loadCrawlResults(process.argv[2]); + const results = studyReferences(crawl.ed); + console.log(results); + })(); +} diff --git a/src/reporting/file-issue-for-review.js b/src/reporting/file-issue-for-review.js index f8c4fc7e..93a2d7ca 100644 --- a/src/reporting/file-issue-for-review.js +++ b/src/reporting/file-issue-for-review.js @@ -4,6 +4,7 @@ */ const { loadCrawlResults } = require('../lib/util'); const { studyBackrefs } = require('../lib/study-backrefs'); +const { studyReferences } = require('../lib/study-refs'); const path = require('path'); const fs = require('fs').promises; const { execSync } = require('child_process'); @@ -50,6 +51,10 @@ function issueWrapper (spec, anomalies, anomalyType) { title = `Non-canonical references in ${spec.title}`; anomalyReport = 'the following links were detected as pointing to outdated URLs'; break; + case 'discontinuedReferences': + title = `Normative references to discontinued specs in ${spec.title}`; + anomalyReport = 'the following normative referenced were detected as pointing to discontinued specifications'; + break; } return { title: titlePrefix + title, @@ -73,7 +78,7 @@ ${issueReport} } if (require.main === module) { - const knownAnomalyTypes = ['brokenLinks', 'outdatedSpecs', 'nonCanonicalRefs']; + const knownAnomalyTypes = ['brokenLinks', 'outdatedSpecs', 'nonCanonicalRefs', 'discontinuedReferences']; let edCrawlResultsPath = process.argv[2]; let trCrawlResultsPath = process.argv[3]; @@ -128,8 +133,10 @@ if (require.main === module) { console.log(`Opening crawl results ${edCrawlResultsPath} and ${trCrawlResultsPath}…`); const crawl = await loadCrawlResults(edCrawlResultsPath, trCrawlResultsPath); console.log('- done'); - console.log('Running back references analysis…'); - const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments); + console.log('Running references analysis…'); + // TODO: if we're not running all the reports, this could run only the + // relevant study function + const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments).concat(studyReferences(crawl.ed)); console.log('- done'); const currentBranch = noGit || execSync('git branch --show-current', { encoding: 'utf8' }).trim(); const needsPush = {}; @@ -145,6 +152,10 @@ if (require.main === module) { console.log(`No known repo for ${spec.title}, skipping`); continue; } + if (spec.standing === "discontinued") { + console.log(`${spec.title} is discontinued, skipping`); + continue; + } const issueMoniker = `${spec.shortname}-${anomalyType.toLowerCase()}`; // is there already a file with that moniker? const issueFilename = path.join('issues/', issueMoniker + '.md'); diff --git a/test/study-refs.js b/test/study-refs.js new file mode 100644 index 00000000..d9cc433e --- /dev/null +++ b/test/study-refs.js @@ -0,0 +1,69 @@ +/** + * Tests the links analysis library. + */ +/* global describe, it */ + +const { studyReferences } = require('../src/lib/study-refs'); +const { assertNbAnomalies, assertAnomaly } = require('./util'); + +const specEdUrl = 'https://w3c.github.io/spec/'; +const specEdUrl2 = 'https://w3c.github.io/spec2/'; +const specEdUrl3 = 'https://w3c.github.io/spec3/'; + +function toRefs (name, url) { + return [ {name, url} ]; +} + + +const populateSpec = (url, refs = [], standing = "good", obsoletedBy) => { + const shortname = url.slice(0, -1).split('/').pop(); + return { + url: url, + refs: { + normative: refs + }, + nightly: { + url + }, + shortname, + standing, + obsoletedBy + }; +}; + +function toEdCrawlResults (standing = "good", replacements) { + return [ + populateSpec(specEdUrl, toRefs("spec2", specEdUrl2)), + populateSpec(specEdUrl2, [], standing, replacements), + populateSpec(specEdUrl3) + ]; +} + +describe('The reference analyser', () => { + it('reports no anomaly if references are not discontinued', () => { + const crawlResult = toEdCrawlResults(); + const report = studyReferences(crawlResult); + assertNbAnomalies(report, 0); + }); + + it('reports a discontinued reference with a replacement', () => { + const crawlResult = toEdCrawlResults("discontinued", ["spec3"]); + const report = studyReferences(crawlResult); + assertNbAnomalies(report, 1); + assertAnomaly(report, 0, { + category: 'refs', + message: /spec3/ + }); + }); + + it('reports a discontinued reference without a replacement', () => { + const crawlResult = toEdCrawlResults("discontinued"); + const report = studyReferences(crawlResult); + assertNbAnomalies(report, 1); + assertAnomaly(report, 0, { + category: 'refs', + message: /no known replacement/ + }); + }); + +});