Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSDoc annotations to templates #4621

Merged
merged 12 commits into from
Apr 15, 2022
5 changes: 5 additions & 0 deletions .changeset/cold-boats-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-svelte': patch
---

Disable type checking by default for non-typescript projects.
18 changes: 12 additions & 6 deletions packages/create-svelte/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,15 @@ async function main() {
})
},
{
type: 'toggle',
name: 'typescript',
message: 'Use TypeScript?',
type: 'select',
name: 'types',
message: 'Add type checking?',
initial: false,
active: 'Yes',
inactive: 'No'
choices: [
{ title: 'Type-checked JavaScript', value: 'checkjs' },
{ title: 'TypeScript', value: 'typescript' },
{ title: 'None', value: null }
]
},
{
type: 'toggle',
Expand Down Expand Up @@ -116,9 +119,12 @@ async function main() {

console.log(bold(green('\nYour project is ready!')));

if (options.typescript) {
if (options.types === 'typescript') {
console.log(bold('✔ Typescript'));
console.log(' Inside Svelte components, use <script lang="ts">');
} else if (options.types === 'checkjs') {
console.log(bold('✔ Type-checked JavaScript'));
console.log(' https://www.typescriptlang.org/tsconfig#checkJs');
}

if (options.eslint) {
Expand Down
18 changes: 11 additions & 7 deletions packages/create-svelte/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import { mkdirp, copy, dist } from './utils.js';
export async function create(cwd, options) {
mkdirp(cwd);

write_template_files(options.template, options.typescript, options.name, cwd);
write_template_files(options.template, options.types, options.name, cwd);
write_common_files(cwd, options, options.name);
}

/**
* @param {string} template
* @param {boolean} typescript
* @param {'typescript' | 'checkjs' | null} types
* @param {string} name
* @param {string} cwd
*/
function write_template_files(template, typescript, name, cwd) {
function write_template_files(template, types, name, cwd) {
const dir = dist(`templates/${template}`);
copy(`${dir}/assets`, cwd, (name) => name.replace('DOT-', '.'));
copy(`${dir}/package.json`, `${cwd}/package.json`);

const manifest = `${dir}/files.${typescript ? 'ts' : 'js'}.json`;
const manifest = `${dir}/files.types=${types}.json`;
const files = /** @type {import('./types/internal').File[]} */ (
JSON.parse(fs.readFileSync(manifest, 'utf-8'))
);
Expand Down Expand Up @@ -83,9 +83,13 @@ function write_common_files(cwd, options, name) {
* @returns {boolean}
*/
function matches_condition(condition, options) {
return condition === 'default' || condition === 'skeleton'
? options.template === condition
: options[condition];
if (condition === 'default' || condition === 'skeleton') {
return options.template === condition;
}
if (condition === 'typescript' || condition === 'checkjs') {
return options.types === condition;
}
return options[condition];
}

/**
Expand Down
179 changes: 101 additions & 78 deletions packages/create-svelte/scripts/build-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { transform } from 'sucrase';
import glob from 'tiny-glob/sync.js';
import { mkdirp, rimraf } from '../utils.js';

/** @param {string} typescript */
function convert_typescript(typescript) {
const transformed = transform(typescript, {
/** @param {string} content */
function convert_typescript(content) {
const transformed = transform(content, {
transforms: ['typescript']
});

Expand All @@ -21,6 +21,11 @@ function convert_typescript(typescript) {
});
}

/** @param {string} content */
function strip_jsdoc(content) {
return content.replace(/\/\*\*[\s\S]+?\*\/[\s\n]+/g, '');
}

/** @param {Set<string>} shared */
async function generate_templates(shared) {
const templates = fs.readdirSync('templates');
Expand All @@ -43,8 +48,12 @@ async function generate_templates(shared) {
const meta_file = path.join(cwd, '.meta.json');
if (!fs.existsSync(meta_file)) throw new Error('Template must have a .meta.json file');

/** @type {import('../types/internal.js').File[]} */
const ts = [];
/** @type {Record<string, import('../types/internal.js').File[]>} */
const types = {
typescript: [],
checkjs: [],
null: []
};

glob('**/*', { cwd, filesOnly: true, dot: true }).forEach((name) => {
// the package.template.json thing is a bit annoying — basically we want
Expand All @@ -64,88 +73,102 @@ async function generate_templates(shared) {
// ignore contents of .gitignore or .ignore
if (!gitignore.accepts(name) || !ignore.accepts(name) || name === '.ignore') return;

if (/\.(js|ts|svelte|svelte\.md)$/.test(name)) {
if (/\.(ts|svelte)$/.test(name)) {
const contents = fs.readFileSync(path.join(cwd, name), 'utf8');
ts.push({
name,
contents
});

if (name.endsWith('.d.ts')) {
if (name.endsWith('app.d.ts')) types.checkjs.push({ name, contents });
types.typescript.push({ name, contents });
} else if (name.endsWith('.ts')) {
const js = convert_typescript(contents);

types.typescript.push({
name,
contents: strip_jsdoc(contents)
});

types.checkjs.push({
name: name.replace(/\.ts$/, '.js'),
contents: js
});

types.null.push({
name: name.replace(/\.ts$/, '.js'),
contents: strip_jsdoc(js)
});
} else {
// we jump through some hoops, rather than just using svelte.preprocess,
// so that the output preserves the original formatting to the extent
// possible (e.g. preserving double line breaks). Sucrase is the best
// tool for the job because it just removes the types; Prettier then
// tidies up the end result
const js_contents = contents.replace(
/<script([^>]+)>([\s\S]+?)<\/script>/g,
(m, attrs, typescript) => {
// Sucrase assumes 'unused' imports (which _are_ used, but only
// in the markup) are type imports, and strips them. This step
// prevents it from drawing that conclusion
const imports = [];
const import_pattern = /import (.+?) from/g;
let import_match;
while ((import_match = import_pattern.exec(typescript))) {
const word_pattern = /[a-z_$][a-z0-9_$]*/gi;
let word_match;
while ((word_match = word_pattern.exec(import_match[1]))) {
imports.push(word_match[0]);
}
}

const suffix = `\n${imports.join(',')}`;

const transformed = transform(typescript + suffix, {
transforms: ['typescript']
}).code.slice(0, -suffix.length);

const contents = prettier
.format(transformed, {
parser: 'babel',
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100
})
.trim()
.replace(/^(.)/gm, '\t$1');

return `<script${attrs.replace(' lang="ts"', '')}>\n${contents}\n</script>`;
}
);

types.typescript.push({
name,
contents: strip_jsdoc(contents)
});

types.checkjs.push({
name,
contents: js_contents
});

types.null.push({
name,
contents: strip_jsdoc(js_contents)
});
}
} else {
const dest = path.join(assets, name.replace(/^\./, 'DOT-'));
mkdirp(path.dirname(dest));
fs.copyFileSync(path.join(cwd, name), dest);
}
});

/** @type {import('../types/internal.js').File[]} */
const js = [];

for (const file of ts) {
// The app.d.ts file makes TS/JS aware of some ambient modules, which are
// also needed for JS projects if people turn on "checkJs" in their jsonfig
if (file.name.endsWith('.d.ts')) {
if (file.name.endsWith('app.d.ts')) js.push(file);
} else if (file.name.endsWith('.ts')) {
js.push({
name: file.name.replace(/\.ts$/, '.js'),
contents: convert_typescript(file.contents)
});
} else if (file.name.endsWith('.svelte')) {
// we jump through some hoops, rather than just using svelte.preprocess,
// so that the output preserves the original formatting to the extent
// possible (e.g. preserving double line breaks). Sucrase is the best
// tool for the job because it just removes the types; Prettier then
// tidies up the end result
const contents = file.contents.replace(
/<script([^>]+)>([\s\S]+?)<\/script>/g,
(m, attrs, typescript) => {
// Sucrase assumes 'unused' imports (which _are_ used, but only
// in the markup) are type imports, and strips them. This step
// prevents it from drawing that conclusion
const imports = [];
const import_pattern = /import (.+?) from/g;
let import_match;
while ((import_match = import_pattern.exec(typescript))) {
const word_pattern = /[a-z_$][a-z0-9_$]*/gi;
let word_match;
while ((word_match = word_pattern.exec(import_match[1]))) {
imports.push(word_match[0]);
}
}

const suffix = `\n${imports.join(',')}`;

const transformed = transform(typescript + suffix, {
transforms: ['typescript']
}).code.slice(0, -suffix.length);

const contents = prettier
.format(transformed, {
parser: 'babel',
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100
})
.trim()
.replace(/^(.)/gm, '\t$1');

return `<script${attrs.replace(' lang="ts"', '')}>\n${contents}\n</script>`;
}
);

js.push({
name: file.name,
contents
});
} else {
js.push(file);
}
}

fs.copyFileSync(meta_file, `${dir}/meta.json`);
fs.writeFileSync(`${dir}/files.ts.json`, JSON.stringify(ts, null, '\t'));
fs.writeFileSync(`${dir}/files.js.json`, JSON.stringify(js, null, '\t'));
fs.writeFileSync(
`${dir}/files.types=typescript.json`,
JSON.stringify(types.typescript, null, '\t')
);
fs.writeFileSync(`${dir}/files.types=checkjs.json`, JSON.stringify(types.checkjs, null, '\t'));
fs.writeFileSync(`${dir}/files.types=null.json`, JSON.stringify(types.null, null, '\t'));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ await create(repo, {
name: 'kit-template-default',
template: 'default',
eslint: false,
typescript: false,
types: 'checkjs',
prettier: true,
playwright: false
});
6 changes: 6 additions & 0 deletions packages/create-svelte/shared/+checkjs/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"checkJs": true
}
}
10 changes: 10 additions & 0 deletions packages/create-svelte/shared/+checkjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"scripts": {
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch"
},
"devDependencies": {
"typescript": "~4.6.2",
"svelte-check": "^2.2.6"
}
}
5 changes: 5 additions & 0 deletions packages/create-svelte/shared/+default+checkjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/cookie": "^0.4.1"
}
}
20 changes: 20 additions & 0 deletions packages/create-svelte/shared/+default+checkjs/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),

kit: {
adapter: adapter(),

// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};

export default config;
3 changes: 0 additions & 3 deletions packages/create-svelte/shared/-typescript/jsconfig.json

This file was deleted.

1 change: 1 addition & 0 deletions packages/create-svelte/templates/default/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cookie from 'cookie';
import { v4 as uuid } from '@lukeed/uuid';
import type { Handle } from '@sveltejs/kit';

/** @type {import('@sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies.userid || uuid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);

/**
* @param {number} n
* @param {number} m
*/
function modulo(n: number, m: number) {
// handle negative numbers
return ((n % m) + m) % m;
Expand Down
Loading