Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b003b78
feat: migrate plugins to TypeScript and update package configurations
clicktodev Nov 7, 2025
44d89cd
fix: correct typo in success message and update script commands to us…
clicktodev Nov 7, 2025
3cf0802
fixes
clicktodev Nov 7, 2025
324bdcb
feat: migrate snackPlayerInitializer and jumpToFragment modules to Ty…
clicktodev Nov 10, 2025
d1bbbb3
fix: update import statements to remove file extensions in lint examp…
clicktodev Nov 10, 2025
084c822
fix: resolve TypeScript type errors in cacheFetch and noDeadUrls func…
clicktodev Nov 10, 2025
1a72f25
fix: add TODO comment for proper type handling in runLinter error catch
clicktodev Nov 10, 2025
0f41b5f
feat: add TypeScript configuration for remark-snackplayer and scripts…
clicktodev Nov 10, 2025
f7ea9a8
fix: update import statements to include file extensions for lintExam…
clicktodev Nov 10, 2025
351b8ef
fix: update tsconfig to include the out directory for lint examples
clicktodev Nov 10, 2025
48201b5
fix: cast response JSON to any type for type safety in getMovies func…
clicktodev Nov 10, 2025
5e62785
Fix type assertion in fetch response handling
clicktodev Nov 10, 2025
baa325a
revert lock changes
clicktodev Nov 10, 2025
8aa90b1
fix: remove comment
clicktodev Nov 10, 2025
0902a3d
dedupe
clicktodev Nov 10, 2025
e476507
fix: improve type safety for movies response in fetch handling
clicktodev Nov 10, 2025
4d6a06e
fix: enhance type safety by defining ExampleMapping interface and upd…
clicktodev Nov 10, 2025
31907d5
fix: improve type safety by replacing type assertions with explicit H…
clicktodev Nov 10, 2025
b35194d
Remove unused import in snackPlayerInitializer
clicktodev Nov 10, 2025
0306fc5
fix: handle RequestError more accurately in naiveLinkCheck function
clicktodev Nov 10, 2025
036fdbf
fix: improve type safety by refining function signatures and removing…
clicktodev Nov 10, 2025
d772be1
typing fillouts and fixes
Simek Nov 11, 2025
b0529cf
run TSC as a part of lint
Simek Nov 11, 2025
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
8 changes: 7 additions & 1 deletion docs/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,20 @@ type Movie = {
releaseYear: string;
};

type MoviesResponse = {
title: string;
description: string;
movies: Movie[];
};

const App = () => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState<Movie[]>([]);

const getMovies = async () => {
try {
const response = await fetch('https://reactnative.dev/movies.json');
const json = await response.json();
const json = (await response.json()) as MoviesResponse;
setData(json.movies);
} catch (error) {
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @format
*/

import lintExamples from '../src/lintExamples.js';
import lintExamples from '../src/lintExamples.ts';

console.log('Linting JSX docs code examples...');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @format
*/

import lintExamples from '../src/lintExamples.js';
import lintExamples from '../src/lintExamples.ts';

console.log('Linting TSX docs code examples...');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @format
*/

import lintExamples from '../src/lintExamples.js';
import lintExamples from '../src/lintExamples.ts';

console.log('Typechecking TSX docs code examples...');

Expand Down
8 changes: 4 additions & 4 deletions packages/lint-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"private": true,
"type": "module",
"bin": {
"eslint-examples-jsx": "./bin/eslint-examples-jsx.js",
"eslint-examples-tsx": "./bin/eslint-examples-tsx.js",
"tsc-examples": "./bin/tsc-examples.js"
"eslint-examples-jsx": "./bin/eslint-examples-jsx.ts",
"eslint-examples-tsx": "./bin/eslint-examples-tsx.ts",
"tsc-examples": "./bin/tsc-examples.ts"
},
"scripts": {
"lint": "eslint"
"lint": "tsc --noEmit && eslint"
},
"devDependencies": {
"@babel/core": "^7.28.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import {glob} from 'glob';

/**
* Represents a mapping between a document and its extracted example
*/
interface ExampleMapping {
documentPath: string;
examplePath: string;
offset: number;
length: number;
}

/**
* The root document to search for documents
*/
Expand All @@ -38,12 +48,22 @@ const validExtensions = ['js', 'tsx'];
* file made by the linter. Commands passed to node are passed to the
* underlying command.
*
* @param opts.command an npx command to run as the linter tool
* @param opts.args extra arguments to be passed to the linter
* @param opts.extension extension to treat the example as if it does not specify one
* @param opts.writeBack whether to update examples with mutations made by the linter
* @param command an npx command to run as the linter tool
* @param args extra arguments to be passed to the linter
* @param extension extension to treat the example as if it does not specify one
* @param writeBack whether to update examples with mutations made by the linter
*/
async function lintExamples({command, args, extension, writeBack}) {
async function lintExamples({
command,
args,
extension,
writeBack,
}: {
command: string;
args?: string[];
extension: string;
writeBack: boolean;
}) {
if (!validExtensions.includes(extension)) {
console.error(
`Invalid extension "${extension}" (should be one of ${JSON.stringify(
Expand Down Expand Up @@ -73,12 +93,12 @@ async function lintExamples({command, args, extension, writeBack}) {
*
* @param extension extension to treat the example as if it does not specify
*/
async function extractExamples(extension) {
async function extractExamples(extension: string): Promise<ExampleMapping[]> {
const documents = await glob.glob('**/*.md', {
cwd: documentsRoot,
absolute: true,
});
const mappings = [];
const mappings: ExampleMapping[] = [];

await fs.mkdir(outputRoot, {recursive: true});
await fs.rm(outputRoot, {recursive: true});
Expand All @@ -99,7 +119,10 @@ async function extractExamples(extension) {
* @param filename absolute filename of the documents root
* @param extension extension to treat the example as if it does not specify
*/
async function extractExamplesFromDocument(filename, extension) {
async function extractExamplesFromDocument(
filename: string,
extension: string,
): Promise<ExampleMapping[]> {
const fileContents = await fs.readFile(filename, {
encoding: 'utf-8',
});
Expand Down Expand Up @@ -167,7 +190,7 @@ async function extractExamplesFromDocument(filename, extension) {
* @param command an npx command to run as the linter tool
* @param args extra arguments to be passed to the linter
*/
async function runLinter(command, args) {
async function runLinter(command: string, args: string[]): Promise<number> {
const combinedArgs = [...processArgs, ...args];

try {
Expand All @@ -178,7 +201,14 @@ async function runLinter(command, args) {

return 0;
} catch (ex) {
return ex.status;
if (
ex instanceof Error &&
'status' in ex &&
typeof ex.status === 'number'
) {
return ex.status;
}
return 1;
}
}

Expand All @@ -187,8 +217,8 @@ async function runLinter(command, args) {
*
* @param mappings file mappings generated by extractExamples()
*/
async function updateDocuments(mappings) {
const mappingsByDocument = {};
async function updateDocuments(mappings: ExampleMapping[]) {
const mappingsByDocument: Record<string, ExampleMapping[]> = {};
for (const mapping of mappings) {
if (mappingsByDocument[mapping.documentPath] === undefined) {
mappingsByDocument[mapping.documentPath] = [];
Expand Down
2 changes: 1 addition & 1 deletion packages/lint-examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "@react-native/typescript-config/tsconfig.json",
"include": ["./out"]
"include": ["./src", "./bin", "./out"]
}
8 changes: 5 additions & 3 deletions plugins/remark-codeblock-language-as-title/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"description": "Remark plugin for using codeblock language as title",
"main": "src/index.js",
"main": "src/index.ts",
"type": "module",
"keywords": [
"remark",
Expand All @@ -14,12 +14,14 @@
"src/*"
],
"scripts": {
"lint": "eslint ."
"lint": "tsc --noEmit && eslint ."
},
"dependencies": {
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"remark": "^15.0.1"
"@types/mdast": "^4.0.4",
"remark": "^15.0.1",
"typescript": "^5.9.2"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/

import {Root} from 'mdast';

export default function codeblockLanguageAsTitleRemarkPlugin() {
/**
* @param {import('mdast').Root} root - The root node of the Markdown AST
* @returns {Promise<void>}
*/
return async root => {
return async (root: Root) => {
const {visit} = await import('unist-util-visit');
visit(root, 'code', node => {
if (node.lang) {
Expand Down
4 changes: 4 additions & 0 deletions plugins/remark-codeblock-language-as-title/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@react-native/typescript-config/tsconfig.json",
"include": ["./src"],
}
9 changes: 5 additions & 4 deletions plugins/remark-lint-no-dead-urls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"description": "Remark linter rule to check for dead urls",
"main": "src/index.js",
"main": "src/index.ts",
"type": "module",
"keywords": [
"remark",
Expand All @@ -14,17 +14,18 @@
"src/*"
],
"scripts": {
"lint": "eslint .",
"lint": "tsc --noEmit && eslint .",
"test": "yarn node --experimental-vm-modules $(yarn bin jest)"
},
"dependencies": {
"got": "^13.0.0",
"got": "^14.6.3",
"unified-lint-rule": "^3.0.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"dedent": "^1.5.3",
"jest": "^29.4.3",
"remark": "^15.0.1"
"remark": "^15.0.1",
"typescript": "^5.9.2"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

// Forked from: https://github.com/davidtheclark/remark-lint-no-dead-urls

import {Method, RequestError} from 'got';
import {Root} from 'mdast';
import {URL} from 'node:url';
import {lintRule} from 'unified-lint-rule';
import {visit} from 'unist-util-visit';
import type {VFile} from 'vfile';

import {fetch} from './lib.js';

Expand All @@ -23,12 +26,17 @@ const HTTP = {
};

const uri = {
isLocalhost: url => /^(https?:\/\/)(localhost|127\.0\.0\.1)(:\d+)?/.test(url),
isExternal: url => /(https?:\/\/)/.test(url),
isPath: url => /^\/.*/.test(url),
isLocalhost: (url: string) =>
/^(https?:\/\/)(localhost|127\.0\.0\.1)(:\d+)?/.test(url),
isExternal: (url: string) => /(https?:\/\/)/.test(url),
isPath: (url: string) => /^\/.*/.test(url),
};

async function cacheFetch(urlOrPath, method, options) {
async function cacheFetch(
urlOrPath: string,
method: Method,
options: {baseUrl?: string} & Record<string, unknown>
) {
if (linkCache.has(urlOrPath)) {
return [urlOrPath, linkCache.get(urlOrPath)];
}
Expand All @@ -42,7 +50,10 @@ async function cacheFetch(urlOrPath, method, options) {
return [urlOrPath, code];
}

async function naiveLinkCheck(urls, options) {
async function naiveLinkCheck(
urls: string[],
options: {baseUrl?: string} & Record<string, unknown>
) {
return Promise.allSettled(
urls.map(async url => {
try {
Expand All @@ -52,10 +63,10 @@ async function naiveLinkCheck(urls, options) {
// Fallback, some endpoints don't support HEAD requests
return await cacheFetch(url, 'GET', options);
} catch (e) {
if (e.code === 'ERR_GOT_REQUEST_ERROR') {
if (!(e instanceof RequestError)) {
throw e;
}
const code = e.statusCode ?? e?.response?.statusCode ?? e.code;
const code = e.response?.statusCode ?? e.code;
linkCache.set(url, code);
return [url, code];
}
Expand All @@ -64,14 +75,21 @@ async function naiveLinkCheck(urls, options) {
);
}

async function noDeadUrls(ast, file, options = {}) {
async function noDeadUrls(
ast: Root,
file: VFile,
options: {
skipUrlPatterns?: string[];
baseUrl?: string;
} & Record<string, unknown> = {}
) {
const urlToNodes = new Map();

const {skipUrlPatterns, ...clientOptions} = options;

// Grab all possible urls from the markdown
visit(ast, ['link', 'image', 'definition'], node => {
const {url} = node;
const {url} = node as {url?: string};
if (
!url ||
uri.isLocalhost(url) ||
Expand All @@ -97,26 +115,28 @@ async function noDeadUrls(ast, file, options = {}) {

const results = await naiveLinkCheck([...urlToNodes.keys()], clientOptions);

for (const {value} of results) {
const [url, statusCode] = value;
const nodes = urlToNodes.get(url) ?? [];
for (const result of results) {
if (result.status === 'fulfilled') {
const [url, statusCode] = result.value;
const nodes = urlToNodes.get(url) ?? [];

if (statusCode === HTTP.OK || statusCode === HTTP.FOUND) {
continue;
}
if (statusCode === HTTP.OK || statusCode === HTTP.FOUND) {
continue;
}

for (const node of nodes) {
switch (statusCode) {
case 'ENOTFOUND':
file.message(`Link to ${url} is broken, domain not found`, node);
break;
case HTTP.TOO_MANY_REQUESTS:
file.message(`Link to ${url} is being rate limited`, node);
break;
case HTTP.NOT_FOUND:
default:
file.message(`Link to ${url} is broken`, node);
break;
for (const node of nodes) {
switch (statusCode) {
case 'ENOTFOUND':
file.message(`Link to ${url} is broken, domain not found`, node);
break;
case HTTP.TOO_MANY_REQUESTS:
file.message(`Link to ${url} is being rate limited`, node);
break;
case HTTP.NOT_FOUND:
default:
file.message(`Link to ${url} is broken`, node);
break;
}
}
}
}
Expand Down
Loading