diff --git a/app/components/crate-sidebar.css b/app/components/crate-sidebar.css
index fed5290a268..57978a25b77 100644
--- a/app/components/crate-sidebar.css
+++ b/app/components/crate-sidebar.css
@@ -22,6 +22,7 @@
.msrv,
.edition,
.license,
+.linecount,
.bytes,
.purl {
display: flex;
@@ -37,7 +38,8 @@
.date,
.msrv,
-.edition {
+.edition,
+.linecount {
> span {
cursor: help;
}
@@ -49,6 +51,7 @@
}
}
+.linecount,
.bytes {
font-variant-numeric: tabular-nums;
}
diff --git a/app/components/crate-sidebar.gjs b/app/components/crate-sidebar.gjs
index 8ab8277a038..eb32fc3bae6 100644
--- a/app/components/crate-sidebar.gjs
+++ b/app/components/crate-sidebar.gjs
@@ -21,6 +21,7 @@ import Tooltip from 'crates-io/components/tooltip';
import dateFormat from 'crates-io/helpers/date-format';
import dateFormatDistanceToNow from 'crates-io/helpers/date-format-distance-to-now';
import dateFormatIso from 'crates-io/helpers/date-format-iso';
+import formatShortNum from 'crates-io/helpers/format-short-num';
import prettyBytes from 'crates-io/helpers/pretty-bytes';
import { simplifyUrl } from './crate-sidebar/link';
@@ -125,6 +126,20 @@ export default class CrateSidebar extends Component {
{{/if}}
+ {{#if @version.linecounts.total_code_lines}}
+
{{svgJar 'weight'}}
diff --git a/app/helpers/format-short-num.js b/app/helpers/format-short-num.js
new file mode 100644
index 00000000000..b74d3d85e3b
--- /dev/null
+++ b/app/helpers/format-short-num.js
@@ -0,0 +1,41 @@
+import Helper from '@ember/component/helper';
+import { service } from '@ember/service';
+
+/**
+ * This matches the implementation in https://github.com/rust-lang/crates_io_og_image/blob/v0.2.1/src/formatting.rs
+ * to ensure that we render roughly the same values in our user interface and the generated OpenGraph images.
+ */
+export default class FormatShortNumHelper extends Helper {
+ @service intl;
+
+ compute([value]) {
+ const THRESHOLD = 1500;
+ const UNITS = ['', 'K', 'M'];
+
+ let numValue = Number(value);
+ let unitIndex = 0;
+
+ // Keep dividing by 1000 until value is below threshold or we've reached the last unit
+ while (numValue >= THRESHOLD && unitIndex < UNITS.length - 1) {
+ numValue /= 1000;
+ unitIndex += 1;
+ }
+
+ let unit = UNITS[unitIndex];
+
+ // Special case for numbers without suffix - no decimal places
+ if (unitIndex === 0) {
+ return this.intl.formatNumber(value);
+ }
+
+ // For K and M, format with appropriate decimal places
+ // Determine number of decimal places to keep number under 4 chars
+ let fractionDigits = numValue < 10 ? 1 : 0;
+ let number = this.intl.formatNumber(numValue, {
+ minimumFractionDigits: fractionDigits,
+ maximumFractionDigits: fractionDigits,
+ });
+
+ return number + unit;
+ }
+}
diff --git a/app/models/version.js b/app/models/version.js
index 83c8cf0bf79..6d57a367f7e 100644
--- a/app/models/version.js
+++ b/app/models/version.js
@@ -25,6 +25,7 @@ export default class Version extends Model {
@attr yanked;
@attr license;
@attr crate_size;
+ @attr linecounts;
/**
* The minimum supported Rust version of this crate version.
diff --git a/app/services/intl.js b/app/services/intl.js
index b9b80c7b3a9..94ed0db0704 100644
--- a/app/services/intl.js
+++ b/app/services/intl.js
@@ -4,7 +4,7 @@ export default class IntlService extends Service {
// `undefined` means "use the default language of the browser"
locale = undefined;
- formatNumber(value) {
- return Number(value).toLocaleString(this.locale);
+ formatNumber(value, options) {
+ return Number(value).toLocaleString(this.locale, options);
}
}
diff --git a/e2e/acceptance/crate.spec.ts b/e2e/acceptance/crate.spec.ts
index 3d18151c0a7..59daa2badf6 100644
--- a/e2e/acceptance/crate.spec.ts
+++ b/e2e/acceptance/crate.spec.ts
@@ -210,6 +210,18 @@ test.describe('Acceptance | crate page', { tag: '@acceptance' }, () => {
await expect(page.locator('[data-test-license]')).toHaveText('MIT OR Apache-2.0');
});
+ test('sidebar shows correct information', async ({ page, msw }) => {
+ let crate = msw.db.crate.create({ name: 'foo' });
+ msw.db.version.create({ crate, num: '0.5.0' });
+ msw.db.version.create({ crate, num: '1.0.0' });
+
+ await page.goto('/crates/foo');
+ await expect(page.locator('[data-test-linecounts]')).toHaveText('1,119 SLoC');
+
+ await page.goto('/crates/foo/0.5.0');
+ await expect(page.locator('[data-test-linecounts]')).toHaveText('520 SLoC');
+ });
+
test.skip('crates can be yanked by owner', async ({ page, msw }) => {
loadFixtures(msw.db);
diff --git a/packages/crates-io-msw/handlers/crates/downloads.test.js b/packages/crates-io-msw/handlers/crates/downloads.test.js
index e588f787de7..2337366adc1 100644
--- a/packages/crates-io-msw/handlers/crates/downloads.test.js
+++ b/packages/crates-io-msw/handlers/crates/downloads.test.js
@@ -91,6 +91,22 @@ test('includes related versions', async function () {
features: {},
id: 1,
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
@@ -113,6 +129,27 @@ test('includes related versions', async function () {
features: {},
id: 2,
license: 'Apache-2.0',
+ linecounts: {
+ languages: {
+ CSS: {
+ code_lines: 503,
+ comment_lines: 42,
+ files: 2,
+ },
+ Python: {
+ code_lines: 284,
+ comment_lines: 91,
+ files: 3,
+ },
+ TypeScript: {
+ code_lines: 332,
+ comment_lines: 83,
+ files: 7,
+ },
+ },
+ total_code_lines: 1119,
+ total_comment_lines: 216,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.1/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.1/downloads',
diff --git a/packages/crates-io-msw/handlers/crates/get.test.js b/packages/crates-io-msw/handlers/crates/get.test.js
index 2dfa077b331..644a614e5b8 100644
--- a/packages/crates-io-msw/handlers/crates/get.test.js
+++ b/packages/crates-io-msw/handlers/crates/get.test.js
@@ -56,6 +56,22 @@ test('returns a crate object for known crates', async function () {
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.0-beta.1/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0-beta.1/downloads',
@@ -121,6 +137,22 @@ test('works for non-canonical names', async function () {
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/foo-bar/1.0.0-beta.1/dependencies',
version_downloads: '/api/v1/crates/foo-bar/1.0.0-beta.1/downloads',
@@ -159,6 +191,17 @@ test('includes related versions', async function () {
downloads: 11_106,
features: {},
license: 'MIT/Apache-2.0',
+ linecounts: {
+ languages: {
+ Python: {
+ code_lines: 421,
+ comment_lines: 64,
+ files: 8,
+ },
+ },
+ total_code_lines: 421,
+ total_comment_lines: 64,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
@@ -181,6 +224,27 @@ test('includes related versions', async function () {
downloads: 7404,
features: {},
license: 'Apache-2.0',
+ linecounts: {
+ languages: {
+ CSS: {
+ code_lines: 503,
+ comment_lines: 42,
+ files: 2,
+ },
+ Python: {
+ code_lines: 284,
+ comment_lines: 91,
+ files: 3,
+ },
+ TypeScript: {
+ code_lines: 332,
+ comment_lines: 83,
+ files: 7,
+ },
+ },
+ total_code_lines: 1119,
+ total_comment_lines: 216,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
@@ -203,6 +267,22 @@ test('includes related versions', async function () {
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
diff --git a/packages/crates-io-msw/handlers/crates/reverse-dependencies.test.js b/packages/crates-io-msw/handlers/crates/reverse-dependencies.test.js
index 88c6ec1642d..57a3eac95d1 100644
--- a/packages/crates-io-msw/handlers/crates/reverse-dependencies.test.js
+++ b/packages/crates-io-msw/handlers/crates/reverse-dependencies.test.js
@@ -76,6 +76,27 @@ test('returns a paginated list of crate versions depending to the specified crat
downloads: 7404,
features: {},
license: 'Apache-2.0',
+ linecounts: {
+ languages: {
+ CSS: {
+ code_lines: 503,
+ comment_lines: 42,
+ files: 2,
+ },
+ Python: {
+ code_lines: 284,
+ comment_lines: 91,
+ files: 3,
+ },
+ TypeScript: {
+ code_lines: 332,
+ comment_lines: 83,
+ files: 7,
+ },
+ },
+ total_code_lines: 1119,
+ total_comment_lines: 216,
+ },
links: {
dependencies: '/api/v1/crates/baz/1.0.1/dependencies',
version_downloads: '/api/v1/crates/baz/1.0.1/downloads',
@@ -98,6 +119,22 @@ test('returns a paginated list of crate versions depending to the specified crat
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/bar/1.0.0/dependencies',
version_downloads: '/api/v1/crates/bar/1.0.0/downloads',
diff --git a/packages/crates-io-msw/handlers/versions/follow-updates.test.js b/packages/crates-io-msw/handlers/versions/follow-updates.test.js
index 49d4ff3c9b6..614aa27adf4 100644
--- a/packages/crates-io-msw/handlers/versions/follow-updates.test.js
+++ b/packages/crates-io-msw/handlers/versions/follow-updates.test.js
@@ -33,6 +33,22 @@ test('returns latest versions of followed crates', async function () {
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/foo/1.2.3/dependencies',
version_downloads: '/api/v1/crates/foo/1.2.3/downloads',
diff --git a/packages/crates-io-msw/handlers/versions/get.test.js b/packages/crates-io-msw/handlers/versions/get.test.js
index 5a37d3eb422..9052552a345 100644
--- a/packages/crates-io-msw/handlers/versions/get.test.js
+++ b/packages/crates-io-msw/handlers/versions/get.test.js
@@ -34,6 +34,22 @@ test('returns a version object for known version', async function () {
features: {},
id: 1,
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.0-beta.1/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0-beta.1/downloads',
diff --git a/packages/crates-io-msw/handlers/versions/list.test.js b/packages/crates-io-msw/handlers/versions/list.test.js
index 01b733d6bf7..9787cc239f5 100644
--- a/packages/crates-io-msw/handlers/versions/list.test.js
+++ b/packages/crates-io-msw/handlers/versions/list.test.js
@@ -39,6 +39,17 @@ test('returns all versions belonging to the specified crate', async function ()
downloads: 11_106,
features: {},
license: 'MIT/Apache-2.0',
+ linecounts: {
+ languages: {
+ Python: {
+ code_lines: 421,
+ comment_lines: 64,
+ files: 8,
+ },
+ },
+ total_code_lines: 421,
+ total_comment_lines: 64,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
@@ -61,6 +72,27 @@ test('returns all versions belonging to the specified crate', async function ()
downloads: 7404,
features: {},
license: 'Apache-2.0',
+ linecounts: {
+ languages: {
+ CSS: {
+ code_lines: 503,
+ comment_lines: 42,
+ files: 2,
+ },
+ Python: {
+ code_lines: 284,
+ comment_lines: 91,
+ files: 3,
+ },
+ TypeScript: {
+ code_lines: 332,
+ comment_lines: 83,
+ files: 7,
+ },
+ },
+ total_code_lines: 1119,
+ total_comment_lines: 216,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
@@ -89,6 +121,22 @@ test('returns all versions belonging to the specified crate', async function ()
downloads: 3702,
features: {},
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
diff --git a/packages/crates-io-msw/handlers/versions/patch.test.js b/packages/crates-io-msw/handlers/versions/patch.test.js
index 829c29c16e8..c2b5531fd6e 100644
--- a/packages/crates-io-msw/handlers/versions/patch.test.js
+++ b/packages/crates-io-msw/handlers/versions/patch.test.js
@@ -64,6 +64,22 @@ test('yanks the version', async function () {
features: {},
id: 1,
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/foo/1.0.0/dependencies',
version_downloads: '/api/v1/crates/foo/1.0.0/downloads',
@@ -95,6 +111,22 @@ test('yanks the version', async function () {
features: {},
id: 1,
license: 'MIT',
+ linecounts: {
+ languages: {
+ JavaScript: {
+ code_lines: 325,
+ comment_lines: 80,
+ files: 8,
+ },
+ TypeScript: {
+ code_lines: 195,
+ comment_lines: 10,
+ files: 2,
+ },
+ },
+ total_code_lines: 520,
+ total_comment_lines: 90,
+ },
links: {
dependencies: '/api/v1/crates/foo/1.0.0/dependencies',
version_downloads: '/api/v1/crates/foo/1.0.0/downloads',
diff --git a/packages/crates-io-msw/models/dependency.test.js b/packages/crates-io-msw/models/dependency.test.js
index b9ad66d9b8c..ae9bc6adfcb 100644
--- a/packages/crates-io-msw/models/dependency.test.js
+++ b/packages/crates-io-msw/models/dependency.test.js
@@ -72,6 +72,22 @@ test('happy path', ({ expect }) => {
"features": {},
"id": 1,
"license": "MIT",
+ "linecounts": {
+ "languages": {
+ "JavaScript": {
+ "code_lines": 325,
+ "comment_lines": 80,
+ "files": 8,
+ },
+ "TypeScript": {
+ "code_lines": 195,
+ "comment_lines": 10,
+ "files": 2,
+ },
+ },
+ "total_code_lines": 520,
+ "total_comment_lines": 90,
+ },
"num": "1.0.0",
"publishedBy": null,
"readme": null,
diff --git a/packages/crates-io-msw/models/version-download.test.js b/packages/crates-io-msw/models/version-download.test.js
index 0dd48be6bca..405b1878682 100644
--- a/packages/crates-io-msw/models/version-download.test.js
+++ b/packages/crates-io-msw/models/version-download.test.js
@@ -42,6 +42,22 @@ test('happy path', ({ expect }) => {
"features": {},
"id": 1,
"license": "MIT",
+ "linecounts": {
+ "languages": {
+ "JavaScript": {
+ "code_lines": 325,
+ "comment_lines": 80,
+ "files": 8,
+ },
+ "TypeScript": {
+ "code_lines": 195,
+ "comment_lines": 10,
+ "files": 2,
+ },
+ },
+ "total_code_lines": 520,
+ "total_comment_lines": 90,
+ },
"num": "1.0.0",
"publishedBy": null,
"readme": null,
diff --git a/packages/crates-io-msw/models/version.js b/packages/crates-io-msw/models/version.js
index bf0b4ec22f9..54801e67698 100644
--- a/packages/crates-io-msw/models/version.js
+++ b/packages/crates-io-msw/models/version.js
@@ -4,6 +4,8 @@ import { applyDefault } from '../utils/defaults.js';
const LICENSES = ['MIT/Apache-2.0', 'MIT', 'Apache-2.0'];
+const LANGUAGES = ['Rust', 'JavaScript', 'TypeScript', 'Python', 'CSS', 'HTML', 'Shell'];
+
export default {
id: primaryKey(Number),
@@ -19,6 +21,7 @@ export default {
readme: nullable(String),
rust_version: nullable(String),
trustpub_data: nullable(Object),
+ linecounts: nullable(Object),
crate: oneOf('crate'),
publishedBy: nullable(oneOf('user')),
@@ -36,9 +39,53 @@ export default {
applyDefault(attrs, 'readme', () => null);
applyDefault(attrs, 'rust_version', () => null);
applyDefault(attrs, 'trustpub_data', () => null);
+ applyDefault(attrs, 'linecounts', () => generateLinecounts(attrs.id));
if (!attrs.crate) {
throw new Error(`Missing \`crate\` relationship on \`version:${attrs.num}\``);
}
},
};
+
+function generateLinecounts(id) {
+ // Some versions don't have linecount data (simulating older versions)
+ if (id % 4 === 0) {
+ return null;
+ }
+
+ const languages = {};
+ let totalCodeLines = 0;
+ let totalCommentLines = 0;
+
+ // Generate 1-3 random languages per version
+ const numLanguages = (id % 3) + 1;
+ const selectedLanguages = [];
+
+ for (let i = 0; i < numLanguages; i++) {
+ const langIndex = (id + i) % LANGUAGES.length;
+ selectedLanguages.push(LANGUAGES[langIndex]);
+ }
+
+ for (const language of selectedLanguages) {
+ // Generate pseudo-random but deterministic line counts based on id and language
+ const seed = id + language.codePointAt(0);
+ const codeLines = ((seed * 137) % 500) + 50; // 50-550 lines
+ const commentLines = ((seed * 73) % 100) + 5; // 5-105 lines
+ const files = ((seed * 29) % 8) + 1; // 1-8 files
+
+ languages[language] = {
+ code_lines: codeLines,
+ comment_lines: commentLines,
+ files: files,
+ };
+
+ totalCodeLines += codeLines;
+ totalCommentLines += commentLines;
+ }
+
+ return {
+ languages,
+ total_code_lines: totalCodeLines,
+ total_comment_lines: totalCommentLines,
+ };
+}
diff --git a/packages/crates-io-msw/models/version.test.js b/packages/crates-io-msw/models/version.test.js
index 8d19e77f28d..9006f703c14 100644
--- a/packages/crates-io-msw/models/version.test.js
+++ b/packages/crates-io-msw/models/version.test.js
@@ -37,6 +37,22 @@ test('happy path', ({ expect }) => {
"features": {},
"id": 1,
"license": "MIT",
+ "linecounts": {
+ "languages": {
+ "JavaScript": {
+ "code_lines": 325,
+ "comment_lines": 80,
+ "files": 8,
+ },
+ "TypeScript": {
+ "code_lines": 195,
+ "comment_lines": 10,
+ "files": 2,
+ },
+ },
+ "total_code_lines": 520,
+ "total_comment_lines": 90,
+ },
"num": "1.0.0",
"publishedBy": null,
"readme": null,
diff --git a/public/assets/code.svg b/public/assets/code.svg
new file mode 100644
index 00000000000..3dfae3ce114
--- /dev/null
+++ b/public/assets/code.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/src/snapshots/crates_io__openapi__tests__openapi_snapshot-2.snap b/src/snapshots/crates_io__openapi__tests__openapi_snapshot-2.snap
index 115f1d84123..f24f7d0488d 100644
--- a/src/snapshots/crates_io__openapi__tests__openapi_snapshot-2.snap
+++ b/src/snapshots/crates_io__openapi__tests__openapi_snapshot-2.snap
@@ -1108,6 +1108,10 @@ expression: response.json()
"null"
]
},
+ "linecounts": {
+ "description": "Line count statistics for this version.\n\nStatus: **Unstable**\n\nThis field may be `null` until the version has been analyzed, which\nhappens in an asynchronous background job.",
+ "type": "object"
+ },
"links": {
"$ref": "#/components/schemas/VersionLinks",
"description": "Links to other API endpoints related to this version."
@@ -1190,7 +1194,8 @@ expression: response.json()
"links",
"crate_size",
"audit_actions",
- "checksum"
+ "checksum",
+ "linecounts"
],
"type": "object"
},
diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__edition__edition_is_saved-4.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__edition__edition_is_saved-4.snap
index 3850557bc57..a96debf1df8 100644
--- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__edition__edition_is_saved-4.snap
+++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__edition__edition_is_saved-4.snap
@@ -33,6 +33,11 @@ expression: response.json()
"id": "[id]",
"lib_links": null,
"license": "MIT",
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ },
"links": {
"authors": "/api/v1/crates/foo/1.0.0/authors",
"dependencies": "/api/v1/crates/foo/1.0.0/dependencies",
diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field-3.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field-3.snap
index 8f18952f4ec..84e59b00384 100644
--- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field-3.snap
+++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__links__crate_with_links_field-3.snap
@@ -33,6 +33,11 @@ expression: response.json()
"id": "[id]",
"lib_links": "git2",
"license": "MIT",
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ },
"links": {
"authors": "/api/v1/crates/foo/1.0.0/authors",
"dependencies": "/api/v1/crates/foo/1.0.0/dependencies",
diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme-4.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme-4.snap
index 5fe5f2e0f12..e2e20b682d4 100644
--- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme-4.snap
+++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__boolean_readme-4.snap
@@ -33,6 +33,11 @@ expression: response.json()
"id": "[id]",
"lib_links": null,
"license": "MIT",
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ },
"links": {
"authors": "/api/v1/crates/foo/1.0.0/authors",
"dependencies": "/api/v1/crates/foo/1.0.0/dependencies",
diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate-4.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate-4.snap
index 606721884a8..a1efb78dcc5 100644
--- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate-4.snap
+++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__manifest__lib_and_bin_crate-4.snap
@@ -36,6 +36,17 @@ expression: response.json()
"id": "[id]",
"lib_links": null,
"license": "MIT",
+ "linecounts": {
+ "languages": {
+ "Rust": {
+ "code_lines": 3,
+ "comment_lines": 0,
+ "files": 3
+ }
+ },
+ "total_code_lines": 3,
+ "total_comment_lines": 0
+ },
"links": {
"authors": "/api/v1/crates/foo/1.0.0/authors",
"dependencies": "/api/v1/crates/foo/1.0.0/dependencies",
diff --git a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__trustpub__full_flow-9.snap b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__trustpub__full_flow-9.snap
index 1ad21be16e3..ea0ac3dfa2f 100644
--- a/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__trustpub__full_flow-9.snap
+++ b/src/tests/krate/publish/snapshots/crates_io__tests__krate__publish__trustpub__full_flow-9.snap
@@ -21,6 +21,11 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": "MIT",
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ },
"links": {
"authors": "/api/v1/crates/foo/1.1.0/authors",
"dependencies": "/api/v1/crates/foo/1.1.0/dependencies",
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-2.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-2.snap
index 9782ad19f8e..a4620b61f50 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-2.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-2.snap
@@ -63,6 +63,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-3.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-3.snap
index 3024140b782..9fa9757feab 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-3.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-3.snap
@@ -74,6 +74,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-4.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-4.snap
index 3024140b782..9fa9757feab 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-4.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-4.snap
@@ -74,6 +74,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-5.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-5.snap
index 8e3a9965e2a..4dcd1c8d08f 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-5.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-5.snap
@@ -85,6 +85,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-6.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-6.snap
index 8e3a9965e2a..4dcd1c8d08f 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-6.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank-6.snap
@@ -85,6 +85,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank.snap b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank.snap
index 9782ad19f8e..a4620b61f50 100644
--- a/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank.snap
+++ b/src/tests/krate/snapshots/crates_io__tests__krate__yanking__patch_version_yank_unyank.snap
@@ -63,6 +63,11 @@ expression: json
"homepage": null,
"documentation": null,
"repository": null,
- "trustpub_data": null
+ "trustpub_data": null,
+ "linecounts": {
+ "languages": {},
+ "total_code_lines": 0,
+ "total_comment_lines": 0
+ }
}
}
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__admin__index_include_yanked.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__admin__index_include_yanked.snap
index 6bc1c1257cb..174c12ac434 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__admin__index_include_yanked.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__admin__index_include_yanked.snap
@@ -1,7 +1,6 @@
---
source: src/tests/routes/crates/admin.rs
expression: response.json()
-snapshot_kind: text
---
{
"crates": [
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version-2.snap
index c5d5669a769..85a9950f33f 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__include_default_version-2.snap
@@ -55,6 +55,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_default_version/0.5.1/authors",
"dependencies": "/api/v1/crates/foo_default_version/0.5.1/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show-2.snap
index 1db5c8d3bcc..0d8f97403d5 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show-2.snap
@@ -68,6 +68,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_show/0.5.1/authors",
"dependencies": "/api/v1/crates/foo_show/0.5.1/dependencies",
@@ -107,6 +108,7 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_show/0.5.0/authors",
"dependencies": "/api/v1/crates/foo_show/0.5.0/dependencies",
@@ -146,6 +148,7 @@ expression: response.json()
"id": 1,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_show/1.0.0/authors",
"dependencies": "/api/v1/crates/foo_show/1.0.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked-2.snap
index b24044455e9..b18dbcdc4b9 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__read__show_all_yanked-2.snap
@@ -67,6 +67,7 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_show/0.5.0/authors",
"dependencies": "/api/v1/crates/foo_show/0.5.0/dependencies",
@@ -106,6 +107,7 @@ expression: response.json()
"id": 1,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_show/1.0.0/authors",
"dependencies": "/api/v1/crates/foo_show/1.0.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__prerelease_versions_not_included_in_reverse_dependencies-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__prerelease_versions_not_included_in_reverse_dependencies-2.snap
index a28a59217da..0100fa322c8 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__prerelease_versions_not_included_in_reverse_dependencies-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__prerelease_versions_not_included_in_reverse_dependencies-2.snap
@@ -39,6 +39,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c3/1.0.0/authors",
"dependencies": "/api/v1/crates/c3/1.0.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies-2.snap
index 5a6e2f0bc53..cf5c91caf66 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies-2.snap
@@ -39,6 +39,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c2/1.1.0/authors",
"dependencies": "/api/v1/crates/c2/1.1.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_includes_published_by_user_when_present-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_includes_published_by_user_when_present-2.snap
index 95e0eefb230..f189d3c5896 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_includes_published_by_user_when_present-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_includes_published_by_user_when_present-2.snap
@@ -51,6 +51,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c3/3.0.0/authors",
"dependencies": "/api/v1/crates/c3/3.0.0/dependencies",
@@ -90,6 +91,7 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c2/2.0.0/authors",
"dependencies": "/api/v1/crates/c2/2.0.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_query_supports_u64_version_number_parts-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_query_supports_u64_version_number_parts-2.snap
index 2f07702d00c..4c6614b2420 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_query_supports_u64_version_number_parts-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_query_supports_u64_version_number_parts-2.snap
@@ -39,6 +39,7 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c2/1.0.18446744073709551615/authors",
"dependencies": "/api/v1/crates/c2/1.0.18446744073709551615/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_when_old_version_doesnt_depend_but_new_does-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_when_old_version_doesnt_depend_but_new_does-2.snap
index 0b9b5db1cf4..e0c4e01afe0 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_when_old_version_doesnt_depend_but_new_does-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__reverse_dependencies_when_old_version_doesnt_depend_but_new_does-2.snap
@@ -39,6 +39,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c2/2.0.0/authors",
"dependencies": "/api/v1/crates/c2/2.0.0/dependencies",
diff --git a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__yanked_versions_not_included_in_reverse_dependencies-2.snap b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__yanked_versions_not_included_in_reverse_dependencies-2.snap
index 0b9b5db1cf4..e0c4e01afe0 100644
--- a/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__yanked_versions_not_included_in_reverse_dependencies-2.snap
+++ b/src/tests/routes/crates/snapshots/crates_io__tests__routes__crates__reverse_dependencies__yanked_versions_not_included_in_reverse_dependencies-2.snap
@@ -39,6 +39,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/c2/2.0.0/authors",
"dependencies": "/api/v1/crates/c2/2.0.0/dependencies",
diff --git a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__list__versions-2.snap b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__list__versions-2.snap
index 92ea2d62e20..521f406a88c 100644
--- a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__list__versions-2.snap
+++ b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__list__versions-2.snap
@@ -26,6 +26,7 @@ expression: response.json()
"id": 2,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_versions/1.0.0/authors",
"dependencies": "/api/v1/crates/foo_versions/1.0.0/dependencies",
@@ -59,6 +60,7 @@ expression: response.json()
"id": 1,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_versions/0.5.1/authors",
"dependencies": "/api/v1/crates/foo_versions/0.5.1/dependencies",
@@ -98,6 +100,7 @@ expression: response.json()
"id": 3,
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_versions/0.5.0/authors",
"dependencies": "/api/v1/crates/foo_versions/0.5.0/dependencies",
diff --git a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_semver_no_published_by.snap b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_semver_no_published_by.snap
index 3e9fd9dbe94..21df33f67dd 100644
--- a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_semver_no_published_by.snap
+++ b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_semver_no_published_by.snap
@@ -21,6 +21,7 @@ expression: json
"id": "[id]",
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_vers_show_no_pb/1.0.0/authors",
"dependencies": "/api/v1/crates/foo_vers_show_no_pb/1.0.0/dependencies",
diff --git a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_version.snap b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_version.snap
index f174100a440..603951ed766 100644
--- a/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_version.snap
+++ b/src/tests/routes/crates/versions/snapshots/crates_io__tests__routes__crates__versions__read__show_by_crate_name_and_version.snap
@@ -21,6 +21,7 @@ expression: json
"id": "[id]",
"lib_links": null,
"license": null,
+ "linecounts": null,
"links": {
"authors": "/api/v1/crates/foo_vers_show/2.0.0/authors",
"dependencies": "/api/v1/crates/foo_vers_show/2.0.0/dependencies",
diff --git a/src/views.rs b/src/views.rs
index 481e3ec50f5..dc9095b289d 100644
--- a/src/views.rs
+++ b/src/views.rs
@@ -924,6 +924,15 @@ pub struct EncodableVersion {
/// inside it.
#[schema(value_type = Option