Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions .github/shared/src/readme.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import { readFile } from "fs/promises";
Expand All @@ -23,7 +23,7 @@
*/
#content;

/** @type {{globalConfig: Object, tags: Set<Tag>} | undefined} */
/** @type {{globalConfig: Object, tags: Map<string, Tag>} | undefined} */
#data;

/** @type {import('./logger.js').ILogger | undefined} */
Expand Down Expand Up @@ -114,8 +114,8 @@
{},
);

/** @type {Set<Tag>} */
const tags = new Set();
/** @type {Map<string, Tag>} */
const tags = new Map();
for (const block of yamlBlocks) {
const tagName =
block.lang?.match(/yaml.*\$\(tag\) ?== ?'([^']*)'/)?.[1] || "default";
Expand Down Expand Up @@ -145,7 +145,7 @@
// swaggers means that the previous definition did not have an input-file
// key. It's possible that the previous defintion had an `input-file: []`
// or something like it.
const existingTag = [...tags].find((t) => t.name == tagName);
const existingTag = tags.get(tagName);
if ((existingTag?.inputFiles?.size ?? 0) > 0) {
// The tag already exists and has a swagger file. This is an error as
// there should only be one definition of input-files per tag.
Expand All @@ -154,8 +154,8 @@
throw new Error(message);
}

/** @type {Set<Swagger>} */
const inputFiles = new Set();
/** @type {Map<string, Swagger>} */
const inputFiles = new Map();

// It's possible for input-file to be a string or an array
const inputFilePaths = Array.isArray(obj["input-file"])
Expand All @@ -172,12 +172,12 @@
logger: this.#logger,
specModel: this.#specModel,
});
inputFiles.add(swagger);
inputFiles.set(swagger.path, swagger);
}

const tag = new Tag(tagName, inputFiles, { logger: this.#logger });

tags.add(tag);
tags.set(tag.name, tag);
}

this.#data = { globalConfig, tags };
Expand All @@ -197,7 +197,7 @@
}

/**
* @returns {Promise<Set<Tag>>}
* @returns {Promise<Map<string, Tag>>}
*/
async getTags() {
return (await this.#getData()).tags;
Expand All @@ -216,7 +216,9 @@
*/
async toJSONAsync(options) {
const tags = await mapAsync(
[...(await this.getTags())].sort((a, b) => a.name.localeCompare(b.name)),
[...(await this.getTags()).values()].sort((a, b) =>
a.name.localeCompare(b.name),
),
async (t) => await t.toJSONAsync(options),
);

Expand Down
69 changes: 34 additions & 35 deletions .github/shared/src/spec-model.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import { readdir } from "fs/promises";
Expand All @@ -21,7 +21,7 @@
/** @type {import('./logger.js').ILogger | undefined} */
#logger;

/** @type {Set<Readme> | undefined} */
/** @type {Map<string, Readme> | undefined} */
#readmes;

/**
Expand All @@ -42,34 +42,35 @@
}

/**
* Given a swagger file, return all the tags inside readme files that reference the file (directly or indirectly).
* @param {string} swaggerPath
* @returns {Promise<Map<Readme, Set<Tag>>>}
* @returns {Promise<Map<string, Map<string, Tag>>>} map of readme paths to (map of tag names to Tag objects)
*/
async getAffectedReadmeTags(swaggerPath) {
const swaggerPathResolved = resolve(swaggerPath);

/** @type {Map<Readme, Set<Tag>>} */
/** @type {Map<string, Map<string, Tag>>} */
const affectedReadmeTags = new Map();

for (const readme of await this.getReadmes()) {
for (const tag of await readme.getTags()) {
for (const inputFile of tag.inputFiles) {
for (const readme of (await this.getReadmes()).values()) {
for (const tag of (await readme.getTags()).values()) {
for (const inputFile of tag.inputFiles.values()) {
if (inputFile.path === swaggerPathResolved) {
/** @type {Set<Tag>} */
const tags = affectedReadmeTags.get(readme) ?? new Set();
tags.add(tag);
affectedReadmeTags.set(readme, tags);
/** @type {Map<string, Tag>} */
const tags = affectedReadmeTags.get(readme.path) ?? new Map();
tags.set(tag.name, tag);
affectedReadmeTags.set(readme.path, tags);

// No need to check refs if the swagger file is directly referenced
continue;
}

const refs = await inputFile.getRefs();
if ([...refs].find((r) => r.path === swaggerPathResolved)) {
/** @type {Set<Tag>} */
const tags = affectedReadmeTags.get(readme) ?? new Set();
tags.add(tag);
affectedReadmeTags.set(readme, tags);
if (refs.get(swaggerPathResolved)) {
/** @type {Map<string, Tag>} */
const tags = affectedReadmeTags.get(readme.path) ?? new Map();
tags.set(tag.name, tag);
affectedReadmeTags.set(readme.path, tags);
}
}
}
Expand All @@ -82,19 +83,17 @@
* Given a swagger file, return the swagger files that are affected by the
* changes in the given swagger file.
* @param {string} swaggerPath
* @returns {Promise<Set<Swagger>>}
* @returns {Promise<Map<string, Swagger>>} map of swagger paths to Swagger objects
*/
async getAffectedSwaggers(swaggerPath) {
const swaggerPathResolved = resolve(swaggerPath);

// Use Map instead of Set, to ensure exactly one Swagger object per path is returned
// SpecModel can include multiple Swagger objects pointing to the same path
/** @type {Map<string, Swagger>} */
const affectedSwaggers = new Map();

for (const readme of await this.getReadmes()) {
for (const tag of await readme.getTags()) {
for (const swagger of tag.inputFiles) {
for (const readme of (await this.getReadmes()).values()) {
for (const tag of (await readme.getTags()).values()) {
for (const swagger of tag.inputFiles.values()) {
// readme.md includes swaggerPath
if (swagger.path === swaggerPathResolved) {
affectedSwaggers.set(swagger.path, swagger);
Expand All @@ -104,9 +103,7 @@

// readme.md includes a.json
// a.json references swaggerPath
const refToSwaggerPath = [...refs].find(
(ref) => ref.path === swaggerPathResolved,
);
const refToSwaggerPath = refs.get(swaggerPathResolved);
if (refToSwaggerPath) {
// Add the Swagger object for swaggerPath
affectedSwaggers.set(refToSwaggerPath.path, refToSwaggerPath);
Expand All @@ -120,11 +117,9 @@
// readme.md includes a.json
// a.json references b.json
// b.json references swaggerPath
for (const ref of refs) {
for (const ref of refs.values()) {
const refRefs = await ref.getRefs();
const refRefToSwaggerPath = [...refRefs].find(
(ref) => ref.path === swaggerPathResolved,
);
const refRefToSwaggerPath = refRefs.get(swaggerPathResolved);
if (refRefToSwaggerPath) {
// Add the Swagger object for swaggerPath
affectedSwaggers.set(
Expand Down Expand Up @@ -160,11 +155,11 @@
);
}

return new Set(affectedSwaggers.values());
return affectedSwaggers;
}

/**
* @returns {Promise<Set<Readme>>}
* @returns {Promise<Map<string, Readme>>} map of readme paths to readme Objects
*/
async getReadmes() {
if (!this.#readmes) {
Expand All @@ -179,10 +174,14 @@

this.#logger?.debug(`Found ${readmePaths.length} readme files`);

this.#readmes = new Set(
readmePaths.map(
(p) => new Readme(p, { logger: this.#logger, specModel: this }),
),
this.#readmes = new Map(
readmePaths.map((p) => {
const readme = new Readme(p, {
logger: this.#logger,
specModel: this,
});
return [readme.path, readme];
}),
);
}

Expand All @@ -195,7 +194,7 @@
*/
async toJSONAsync(options) {
const readmes = await mapAsync(
[...(await this.getReadmes())].sort((a, b) =>
[...(await this.getReadmes()).values()].sort((a, b) =>
a.path.localeCompare(b.path),
),
async (r) => await r.toJSONAsync(options),
Expand Down
22 changes: 11 additions & 11 deletions .github/shared/src/swagger.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import $RefParser from "@apidevtools/json-schema-ref-parser";
Expand All @@ -16,7 +16,7 @@
/** @type {string} absolute path */
#path;

/** @type {Set<Swagger> | undefined} */
/** @type {Map<string, Swagger> | undefined} */
#refs;

/** @type {SpecModel | undefined} backpointer to owning SpecModel */
Expand All @@ -35,7 +35,7 @@
}

/**
* @returns {Promise<Set<Swagger>>}
* @returns {Promise<Map<string, Swagger>>}
*/
async getRefs() {
if (!this.#refs) {
Expand All @@ -50,14 +50,14 @@
// Exclude ourself
.filter((p) => resolve(p) !== resolve(this.#path));

this.#refs = new Set(
refPaths.map(
(p) =>
new Swagger(p, {
logger: this.#logger,
specModel: this.#specModel,
}),
),
this.#refs = new Map(
refPaths.map((p) => {
const swagger = new Swagger(p, {
logger: this.#logger,
specModel: this.#specModel,
});
return [swagger.path, swagger];
}),
);
}

Expand All @@ -83,7 +83,7 @@
: this.#path,
refs: options?.includeRefs
? await mapAsync(
[...(await this.getRefs())].sort((a, b) =>
[...(await this.getRefs()).values()].sort((a, b) =>
a.path.localeCompare(b.path),
),
async (s) =>
Expand Down
10 changes: 6 additions & 4 deletions .github/shared/src/tag.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import { mapAsync } from "./array.js";
Expand All @@ -8,7 +8,7 @@
*/

export class Tag {
/** @type {Set<Swagger>} */
/** @type {Map<string, Swagger>} */
#inputFiles;

/** @type {import('./logger.js').ILogger | undefined} */
Expand All @@ -19,7 +19,7 @@

/**
* @param {string} name
* @param {Set<Swagger>} inputFiles
* @param {Map<string, Swagger>} inputFiles
* @param {Object} [options]
* @param {import('./logger.js').ILogger} [options.logger]
*/
Expand All @@ -30,7 +30,7 @@
}

/**
* @returns {Set<Swagger>}
* @returns {Map<string, Swagger>}
*/
get inputFiles() {
return this.#inputFiles;
Expand All @@ -51,7 +51,9 @@
return {
name: this.#name,
inputFiles: await mapAsync(
[...this.#inputFiles].sort(),
[...this.#inputFiles.values()].sort((a, b) =>
a.path.localeCompare(b.path),
),
async (s) => await s.toJSONAsync(options),
),
};
Expand Down
19 changes: 10 additions & 9 deletions .github/shared/test/readme.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// @ts-check

import { resolve } from "path";
Expand Down Expand Up @@ -26,26 +26,27 @@
});

const tags = await readme.getTags();
const tagNames = new Set([...tags].map((t) => t.name));
const expectedTagNames = new Set([
const tagNames = [...tags.keys()];
const expectedTagNames = [
"package-2021-11-01",
"package-2021-10-01-preview",
]);
];

expect(tagNames).toEqual(expectedTagNames);
expect(tagNames.sort()).toEqual(expectedTagNames.sort());

const swaggers = [...tags].flatMap((t) => [...t.inputFiles]);
const swaggerPaths = new Set(swaggers.map((s) => s.path));
const swaggerPaths = [...tags.values()].flatMap((t) => [
...t.inputFiles.keys(),
]);

const expectedPaths = new Set([
const expectedPaths = [
resolve(folder, "Microsoft.Contoso/stable/2021-11-01/contoso.json"),
resolve(
folder,
"Microsoft.Contoso/preview/2021-10-01-preview/contoso.json",
),
]);
];

expect(swaggerPaths).toEqual(expectedPaths);
expect(swaggerPaths.sort()).toEqual(expectedPaths.sort());
});

it("can be created with empty content", async () => {
Expand Down
Loading
Loading