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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
2 changes: 2 additions & 0 deletions .changeset/tasty-parrots-teach.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/user-organization-invitation-resource.mdx",
"types/user-resource.mdx",
"types/without.mdx",
"shared/api-url-from-publishable-key.mdx",

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever we add JSDoc comments to previously undocumented functions we'll need to update this snapshot as Typedoc will only generate MDX files for documented, exported functions.

You can update the snapshot by running pnpm test:typedoc -u

"shared/build-clerk-js-script-attributes.mdx",
"shared/camel-to-snake.mdx",
"shared/clerk-js-script-url.mdx",
"shared/clerk-runtime-error.mdx",
"shared/create-path-matcher.mdx",
Expand All@@ -79,11 +81,13 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/fast-deep-merge-and-replace.mdx",
"shared/get-clerk-js-major-version-or-tag.mdx",
"shared/get-env-variable.mdx",
"shared/get-non-undefined-values.mdx",
"shared/get-script-url.mdx",
"shared/icon-image-url.mdx",
"shared/in-browser.mdx",
"shared/is-browser-online.mdx",
"shared/is-clerk-runtime-error.mdx",
"shared/is-ipv4-address.mdx",
"shared/is-publishable-key.mdx",
"shared/is-staging.mdx",
"shared/is-truthy.mdx",
Expand All@@ -96,6 +100,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"shared/paginated-resources.mdx",
"shared/read-json-file.mdx",
"shared/set-clerk-js-loading-error-package-name.mdx",
"shared/snake-to-camel.mdx",
"shared/titleize.mdx",
"shared/to-sentence.mdx",
"shared/use-clerk.mdx",
"shared/use-organization-list-params.mdx",
Expand Down
40 changes: 26 additions & 14 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
import pluginJsDoc from 'eslint-plugin-jsdoc';
import globals from 'globals';
import tseslint from 'typescript-eslint';

Expand DownExpand Up@@ -419,21 +420,32 @@ export default tseslint.config([
'turbo/no-undeclared-env-vars': 'off',
},
},
...pluginYml.configs['flat/recommended'],
{
name: 'repo/.github',
// rules: {
// 'regex/invalid': [
// 'error',
// [
// {
// regex: '^(?!.*\\$TURBO_ARGS( |$)).*turbo \\S+',
// message: 'Invalid turbo CI command. Must contain `$TURBO_ARGS`',
// },
// ],
// ],
// },
name: 'repo/jsdoc',
...pluginJsDoc.configs['flat/recommended-typescript'],
files: ['packages/shared/src/**/*.{ts,tsx}'],
ignores: ['**/__tests__/**'],
plugins: {
jsdoc: pluginJsDoc,
},
rules: {
...pluginJsDoc.configs['flat/recommended-typescript'].rules,
'jsdoc/check-examples': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to raise false positives in the examples, we want more control over it

'jsdoc/informative-docs': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that this doesn't happen:

/** The user id. */letuserId;

You have to write a longer description, not restate the name

'jsdoc/check-tag-names': ['warn', { definedTags: ['inline', 'unionReturnHeadings'], typed: false }],

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will warn when an unknown/invalid tag name is used

'jsdoc/require-hyphen-before-param-description': 'warn',
'jsdoc/require-description': 'warn',
'jsdoc/require-description-complete-sentence': 'warn',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentences end with fullstop and start with capital letter

'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }],
'jsdoc/require-param-description': 'warn',
'jsdoc/require-returns': 'off',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Together with require-param I turned these off (unless you define at least one @param) since we use TypeScript types in most places. It doesn't hurt to add them but I don't think it's necessary in most places

'jsdoc/tag-lines': [

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures empty lines between tags/description, unless for multiple @param

'warn',
'always',
{ count: 1, applyToEndTag: false, startLines: 1, tags: { param: { lines: 'never' } } },
],
},
},

...pluginYml.configs['flat/recommended'],
configPrettier,
]);
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,7 @@
"eslint-import-resolver-typescript": "3.10.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsdoc": "50.6.9",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-react": "7.37.5",
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/apiUrlFromPublishableKey.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,12 @@ import {
} from './constants';
import { parsePublishableKey } from './keys';

/**
* Get the correct API url based on the publishable key.
*
* @param publishableKey - The publishable key to parse.
* @returns One of Clerk's API URLs.
*/
export const apiUrlFromPublishableKey = (publishableKey: string) => {
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;

Expand Down
39 changes: 34 additions & 5 deletions packages/shared/src/underscore.ts

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this file as an exercise and check how the rules will apply

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
/**
* Converts an array of strings to a comma-separated sentence
* @param items {Array<string>}
* @returns {string} Returns a string with the items joined by a comma and the last item joined by ", or"
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
Expand All@@ -19,19 +20,41 @@ export const toSentence = (items: string[]): string => {
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}

/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}

/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
Expand DownExpand Up@@ -73,6 +96,7 @@ const createDeepObjectTransformer = (transform: any) => {
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
Expand All@@ -81,13 +105,15 @@ export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);

/**
* Returns true for `true`, true, positive numbers.
* Returns false for `false`, false, 0, negative integers and anything else.
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
Expand DownExpand Up@@ -125,6 +151,9 @@ export function isTruthy(value: unknown): boolean {
return false;
}

/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
Expand Down
Loading