summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Erika <3019731+Princesseuh@users.noreply.github.com> 2023-08-02 19:12:03 +0200
committerGravatar Emanuele Stoppa <my.burning@gmail.com> 2023-08-08 11:02:51 +0100
commit997a0db8a4e3851edd69384cf5eadbb969e1d547 (patch)
tree26c769f5dcdcf04189b48bc8714b71f9994d56e0
parent32669cd47555e9c7433c3998a2b6e624dfb2d8e9 (diff)
downloadastro-997a0db8a4e3851edd69384cf5eadbb969e1d547.tar.gz
astro-997a0db8a4e3851edd69384cf5eadbb969e1d547.tar.zst
astro-997a0db8a4e3851edd69384cf5eadbb969e1d547.zip
feat: use external @astrojs/check (#7892)
* feat: use @astrojs/check * fix: what happened in my rebase?? * nit: adjust with feedback
Diffstat (limited to '')
-rw-r--r--.changeset/three-onions-repeat.md5
-rw-r--r--packages/astro/package.json3
-rw-r--r--packages/astro/src/cli/check/index.ts413
-rw-r--r--packages/astro/src/cli/check/print.ts119
-rw-r--r--packages/astro/src/cli/index.ts16
-rw-r--r--packages/astro/src/cli/install-package.ts124
6 files changed, 160 insertions, 520 deletions
diff --git a/.changeset/three-onions-repeat.md b/.changeset/three-onions-repeat.md
new file mode 100644
index 000000000..1781defcc
--- /dev/null
+++ b/.changeset/three-onions-repeat.md
@@ -0,0 +1,5 @@
+---
+'astro': major
+---
+
+The `astro check` command now requires an external package `@astrojs/check` and an install of `typescript` in your project. This was done in order to make the main `astro` package smaller and give more flexibility to users in regard to the version of TypeScript they use.
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 32e016073..9618d49e4 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -117,7 +117,6 @@
"dependencies": {
"@astrojs/compiler": "^1.8.0",
"@astrojs/internal-helpers": "workspace:*",
- "@astrojs/language-server": "^1.0.0",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
"@babel/core": "^7.22.5",
@@ -165,7 +164,6 @@
"string-width": "^5.1.2",
"strip-ansi": "^7.1.0",
"tsconfig-resolver": "^3.0.1",
- "typescript": "*",
"unist-util-visit": "^4.1.2",
"vfile": "^5.3.7",
"vite": "^4.4.6",
@@ -197,6 +195,7 @@
"@types/send": "^0.17.1",
"@types/server-destroy": "^1.0.1",
"@types/unist": "^2.0.6",
+ "@astrojs/check": "^0.1.0",
"astro-scripts": "workspace:*",
"chai": "^4.3.7",
"cheerio": "1.0.0-rc.12",
diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts
index 96bee308d..5ad031714 100644
--- a/packages/astro/src/cli/check/index.ts
+++ b/packages/astro/src/cli/check/index.ts
@@ -1,396 +1,33 @@
-import {
- AstroCheck,
- DiagnosticSeverity,
- type GetDiagnosticsResult,
-} from '@astrojs/language-server';
-import type { FSWatcher } from 'chokidar';
-import glob from 'fast-glob';
-import { bold, dim, red, yellow } from 'kleur/colors';
-import { createRequire } from 'module';
-import fs from 'node:fs';
-import { join } from 'node:path';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import ora from 'ora';
-import type { Arguments as Flags } from 'yargs-parser';
-import type { AstroSettings } from '../../@types/astro';
-import { resolveConfig } from '../../core/config/config.js';
-import { createNodeLogging } from '../../core/config/logging.js';
-import { createSettings } from '../../core/config/settings.js';
-import type { LogOptions } from '../../core/logger/core.js';
-import { debug, info } from '../../core/logger/core.js';
-import { printHelp } from '../../core/messages.js';
-import type { syncInternal } from '../../core/sync';
-import { eventCliSession, telemetry } from '../../events/index.js';
-import { runHookConfigSetup } from '../../integrations/index.js';
-import { flagsToAstroInlineConfig } from '../flags.js';
-import { printDiagnostic } from './print.js';
-
-type DiagnosticResult = {
- errors: number;
- warnings: number;
- hints: number;
-};
-
-export type CheckPayload = {
- /**
- * Flags passed via CLI
- */
- flags: Flags;
-};
-
-type CheckFlags = {
- /**
- * Whether the `check` command should watch for `.astro` and report errors
- * @default {false}
- */
- watch: boolean;
-};
-
-/**
- *
- * Types of response emitted by the checker
- */
-export enum CheckResult {
- /**
- * Operation finished without errors
- */
- ExitWithSuccess,
- /**
- * Operation finished with errors
- */
- ExitWithError,
- /**
- * The consumer should not terminate the operation
- */
- Listen,
-}
-
-const ASTRO_GLOB_PATTERN = '**/*.astro';
-
-/**
- * Checks `.astro` files for possible errors.
- *
- * If the `--watch` flag is provided, the command runs indefinitely and provides diagnostics
- * when `.astro` files are modified.
- *
- * Every time an astro files is modified, content collections are also generated.
- *
- * @param {CheckPayload} options Options passed {@link AstroChecker}
- * @param {Flags} options.flags Flags coming from the CLI
- */
-export async function check({ flags }: CheckPayload): Promise<AstroChecker | undefined> {
- if (flags.help || flags.h) {
- printHelp({
- commandName: 'astro check',
- usage: '[...flags]',
- tables: {
- Flags: [
- ['--watch', 'Watch Astro files for changes and re-run checks.'],
- ['--help (-h)', 'See all available flags.'],
- ],
- },
- description: `Runs diagnostics against your project and reports errors to the console.`,
- });
- return;
- }
-
- // Load settings
- const inlineConfig = flagsToAstroInlineConfig(flags);
- const logging = createNodeLogging(inlineConfig);
- const { userConfig, astroConfig } = await resolveConfig(inlineConfig, 'check');
- telemetry.record(eventCliSession('check', userConfig, flags));
- const settings = createSettings(astroConfig, fileURLToPath(astroConfig.root));
-
- const checkFlags = parseFlags(flags);
- if (checkFlags.watch) {
- info(logging, 'check', 'Checking files in watch mode');
- } else {
- info(logging, 'check', 'Checking files');
- }
-
- const { syncInternal } = await import('../../core/sync/index.js');
- const root = settings.config.root;
- const require = createRequire(import.meta.url);
- const diagnosticChecker = new AstroCheck(
- root.toString(),
- require.resolve('typescript/lib/tsserverlibrary.js', {
- paths: [root.toString()],
- })
- );
-
- return new AstroChecker({
- syncInternal,
- settings,
- fileSystem: fs,
+import path from 'node:path';
+import type { Arguments } from 'yargs-parser';
+import { error, info } from '../../core/logger/core.js';
+import { createLoggingFromFlags } from '../flags.js';
+import { getPackage } from '../install-package.js';
+
+export async function check(flags: Arguments) {
+ const logging = createLoggingFromFlags(flags);
+ const getPackageOpts = { skipAsk: flags.yes || flags.y, cwd: flags.root };
+ const checkPackage = await getPackage<typeof import('@astrojs/check')>(
+ '@astrojs/check',
logging,
- diagnosticChecker,
- isWatchMode: checkFlags.watch,
- });
-}
-
-type CheckerConstructor = {
- diagnosticChecker: AstroCheck;
-
- isWatchMode: boolean;
-
- syncInternal: typeof syncInternal;
-
- settings: Readonly<AstroSettings>;
-
- logging: Readonly<LogOptions>;
-
- fileSystem: typeof fs;
-};
-
-/**
- * Responsible to check files - classic or watch mode - and report diagnostics.
- *
- * When in watch mode, the class does a whole check pass, and then starts watching files.
- * When a change occurs to an `.astro` file, the checker builds content collections again and lint all the `.astro` files.
- */
-export class AstroChecker {
- readonly #diagnosticsChecker: AstroCheck;
- readonly #shouldWatch: boolean;
- readonly #syncInternal: CheckerConstructor['syncInternal'];
-
- readonly #settings: AstroSettings;
-
- readonly #logging: LogOptions;
- readonly #fs: typeof fs;
- #watcher?: FSWatcher;
-
- #filesCount: number;
- #updateDiagnostics: NodeJS.Timeout | undefined;
-
- constructor({
- diagnosticChecker,
- isWatchMode,
- syncInternal,
- settings,
- fileSystem,
- logging,
- }: CheckerConstructor) {
- this.#diagnosticsChecker = diagnosticChecker;
- this.#shouldWatch = isWatchMode;
- this.#syncInternal = syncInternal;
- this.#logging = logging;
- this.#settings = settings;
- this.#fs = fileSystem;
- this.#filesCount = 0;
- }
-
- /**
- * Check all `.astro` files once and then finishes the operation.
- */
- public async check(): Promise<CheckResult> {
- return await this.#checkAllFiles(true);
- }
-
- /**
- * Check all `.astro` files and then start watching for changes.
- */
- public async watch(): Promise<CheckResult> {
- await this.#checkAllFiles(true);
- await this.#watch();
- return CheckResult.Listen;
- }
-
- /**
- * Stops the watch. It terminates the inner server.
- */
- public async stop() {
- await this.#watcher?.close();
- }
-
- /**
- * Whether the checker should run in watch mode
- */
- public get isWatchMode(): boolean {
- return this.#shouldWatch;
- }
-
- async #openDocuments() {
- this.#filesCount = await openAllDocuments(
- this.#settings.config.root,
- [],
- this.#diagnosticsChecker
- );
- }
-
- /**
- * Lint all `.astro` files, and report the result in console. Operations executed, in order:
- * 1. Compile content collections.
- * 2. Optionally, traverse the file system for `.astro` files and saves their paths.
- * 3. Get diagnostics for said files and print the result in console.
- *
- * @param openDocuments Whether the operation should open all `.astro` files
- */
- async #checkAllFiles(openDocuments: boolean): Promise<CheckResult> {
- // Run `astro:config:setup` before syncing to initialize integrations.
- // We do this manually as we're calling `syncInternal` directly.
- const syncSettings = await runHookConfigSetup({
- settings: this.#settings,
- logging: this.#logging,
- command: 'build',
- });
- const processExit = await this.#syncInternal(syncSettings, {
- logging: this.#logging,
- fs: this.#fs,
- });
- // early exit on sync failure
- if (processExit === 1) return processExit;
-
- let spinner = ora(
- ` Getting diagnostics for Astro files in ${fileURLToPath(this.#settings.config.root)}…`
- ).start();
-
- if (openDocuments) {
- await this.#openDocuments();
- }
-
- let diagnostics = await this.#diagnosticsChecker.getDiagnostics();
-
- spinner.succeed();
-
- let brokenDownDiagnostics = this.#breakDownDiagnostics(diagnostics);
- this.#logDiagnosticsSeverity(brokenDownDiagnostics);
- return brokenDownDiagnostics.errors > 0
- ? CheckResult.ExitWithError
- : CheckResult.ExitWithSuccess;
- }
-
- #checkForDiagnostics() {
- clearTimeout(this.#updateDiagnostics);
- // @ematipico: I am not sure of `setTimeout`. I would rather use a debounce but let's see if this works.
- // Inspiration from `svelte-check`.
- this.#updateDiagnostics = setTimeout(async () => await this.#checkAllFiles(false), 500);
- }
-
- /**
- * This function is responsible to attach events to the server watcher
- */
- async #watch() {
- const { default: chokidar } = await import('chokidar');
- this.#watcher = chokidar.watch(
- join(fileURLToPath(this.#settings.config.root), ASTRO_GLOB_PATTERN),
- {
- ignored: ['**/node_modules/**'],
- ignoreInitial: true,
- }
- );
-
- this.#watcher.on('add', (file) => {
- this.#addDocument(file);
- this.#filesCount += 1;
- this.#checkForDiagnostics();
- });
- this.#watcher.on('change', (file) => {
- this.#addDocument(file);
- this.#checkForDiagnostics();
- });
- this.#watcher.on('unlink', (file) => {
- this.#diagnosticsChecker.removeDocument(file);
- this.#filesCount -= 1;
- this.#checkForDiagnostics();
- });
- }
-
- /**
- * Add a document to the diagnostics checker
- * @param filePath Path to the file
- */
- #addDocument(filePath: string) {
- const text = fs.readFileSync(filePath, 'utf-8');
- this.#diagnosticsChecker.upsertDocument({
- uri: pathToFileURL(filePath).toString(),
- text,
- });
- }
+ getPackageOpts,
+ ['typescript']
+ );
+ const typescript = await getPackage('typescript', logging, getPackageOpts);
- /**
- * Logs the result of the various diagnostics
- *
- * @param result Result emitted by AstroChecker.#breakDownDiagnostics
- */
- #logDiagnosticsSeverity(result: Readonly<DiagnosticResult>) {
- info(
- this.#logging,
- 'diagnostics',
- [
- bold(`Result (${this.#filesCount} file${this.#filesCount === 1 ? '' : 's'}): `),
- bold(red(`${result.errors} ${result.errors === 1 ? 'error' : 'errors'}`)),
- bold(yellow(`${result.warnings} ${result.warnings === 1 ? 'warning' : 'warnings'}`)),
- dim(`${result.hints} ${result.hints === 1 ? 'hint' : 'hints'}\n`),
- ].join(`\n${dim('-')} `)
+ if (!checkPackage || !typescript) {
+ error(
+ logging,
+ 'check',
+ 'The `@astrojs/check` and `typescript` packages are required for this command to work. Please manually install them into your project and try again.'
);
+ return;
}
- /**
- * It loops through all diagnostics and break down diagnostics that are errors, warnings or hints.
- */
- #breakDownDiagnostics(diagnostics: Readonly<GetDiagnosticsResult[]>): DiagnosticResult {
- let result: DiagnosticResult = {
- errors: 0,
- warnings: 0,
- hints: 0,
- };
-
- diagnostics.forEach((diag) => {
- diag.diagnostics.forEach((d) => {
- info(this.#logging, 'diagnostics', `\n ${printDiagnostic(diag.fileUri, diag.text, d)}`);
-
- switch (d.severity) {
- case DiagnosticSeverity.Error: {
- result.errors++;
- break;
- }
- case DiagnosticSeverity.Warning: {
- result.warnings++;
- break;
- }
- case DiagnosticSeverity.Hint: {
- result.hints++;
- break;
- }
- }
- });
- });
-
- return result;
- }
-}
-
-/**
- * Open all Astro files in the given directory and return the number of files found.
- */
-async function openAllDocuments(
- workspaceUri: URL,
- filePathsToIgnore: string[],
- checker: AstroCheck
-): Promise<number> {
- const files = await glob(ASTRO_GLOB_PATTERN, {
- cwd: fileURLToPath(workspaceUri),
- ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)),
- absolute: true,
- });
-
- for (const file of files) {
- debug('check', `Adding file ${file} to the list of files to check.`);
- const text = fs.readFileSync(file, 'utf-8');
- checker.upsertDocument({
- uri: pathToFileURL(file).toString(),
- text,
- });
- }
+ const { check: checker, parseArgsAsCheckConfig } = checkPackage;
- return files.length;
-}
+ const config = parseArgsAsCheckConfig(process.argv);
-/**
- * Parse flags and sets defaults
- */
-function parseFlags(flags: Flags): CheckFlags {
- return {
- watch: flags.watch ?? false,
- };
+ info(logging, 'check', `Getting diagnostics for Astro files in ${path.resolve(config.root)}...`);
+ return await checker(config);
}
diff --git a/packages/astro/src/cli/check/print.ts b/packages/astro/src/cli/check/print.ts
deleted file mode 100644
index bd8de2ddb..000000000
--- a/packages/astro/src/cli/check/print.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { DiagnosticSeverity, offsetAt, type Diagnostic } from '@astrojs/language-server';
-import {
- bgRed,
- bgWhite,
- bgYellow,
- black,
- bold,
- cyan,
- gray,
- red,
- white,
- yellow,
-} from 'kleur/colors';
-import { fileURLToPath } from 'node:url';
-import stringWidth from 'string-width';
-
-export function printDiagnostic(filePath: string, text: string, diag: Diagnostic): string {
- let result = [];
-
- // Lines and characters are 0-indexed, so we need to add 1 to the offset to get the actual line and character
- const realStartLine = diag.range.start.line + 1;
- const realStartCharacter = diag.range.start.character + 1;
-
- // IDE friendly path that user can CTRL+Click to open the file at a specific line / character
- const IDEFilePath = `${bold(cyan(fileURLToPath(filePath)))}:${bold(yellow(realStartLine))}:${bold(
- yellow(realStartCharacter)
- )}`;
- result.push(
- `${IDEFilePath} ${bold(getColorForSeverity(diag, getStringForSeverity(diag)))}: ${diag.message}`
- );
-
- // Optionally add the line before the error to add context if not empty
- const previousLine = getLine(diag.range.start.line - 1, text);
- if (previousLine) {
- result.push(`${getPrintableLineNumber(realStartLine - 1)} ${gray(previousLine)}`);
- }
-
- // Add the line with the error
- const str = getLine(diag.range.start.line, text);
- const lineNumStr = realStartLine.toString().padStart(2, '0');
- const lineNumLen = lineNumStr.length;
- result.push(`${getBackgroundForSeverity(diag, lineNumStr)} ${str}`);
-
- // Adds tildes under the specific range where the diagnostic is
- const tildes = generateString('~', diag.range.end.character - diag.range.start.character);
-
- // NOTE: This is not perfect, if the line include any characters that is made of multiple characters, for example
- // regionals flags, but the terminal can't display it, then the number of spaces will be wrong. Not sure how to fix.
- const beforeChars = stringWidth(str.substring(0, diag.range.start.character));
- const spaces = generateString(' ', beforeChars + lineNumLen - 1);
- result.push(` ${spaces}${bold(getColorForSeverity(diag, tildes))}`);
-
- const nextLine = getLine(diag.range.start.line + 1, text);
- if (nextLine) {
- result.push(`${getPrintableLineNumber(realStartLine + 1)} ${gray(nextLine)}`);
- }
-
- // Force a new line at the end
- result.push('');
-
- return result.join('\n');
-}
-
-function generateString(str: string, len: number): string {
- return Array.from({ length: len }, () => str).join('');
-}
-
-function getStringForSeverity(diag: Diagnostic): string {
- switch (diag.severity) {
- case DiagnosticSeverity.Error:
- return 'Error';
- case DiagnosticSeverity.Warning:
- return 'Warning';
- case DiagnosticSeverity.Hint:
- return 'Hint';
- default:
- return 'Unknown';
- }
-}
-
-function getColorForSeverity(diag: Diagnostic, text: string): string {
- switch (diag.severity) {
- case DiagnosticSeverity.Error:
- return red(text);
- case DiagnosticSeverity.Warning:
- return yellow(text);
- case DiagnosticSeverity.Hint:
- return gray(text);
- default:
- return text;
- }
-}
-
-function getBackgroundForSeverity(diag: Diagnostic, text: string): string {
- switch (diag.severity) {
- case DiagnosticSeverity.Error:
- return bgRed(white(text));
- case DiagnosticSeverity.Warning:
- return bgYellow(white(text));
- case DiagnosticSeverity.Hint:
- return bgWhite(black(text));
- default:
- return text;
- }
-}
-
-function getPrintableLineNumber(line: number): string {
- return bgWhite(black(line.toString().padStart(2, '0')));
-}
-
-function getLine(line: number, text: string): string {
- return text
- .substring(
- offsetAt({ line, character: 0 }, text),
- offsetAt({ line, character: Number.MAX_SAFE_INTEGER }, text)
- )
- .replace(/\t/g, ' ')
- .trimEnd();
-}
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index d16ea91e2..fdf43201f 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -154,18 +154,12 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
}
case 'check': {
const { check } = await import('./check/index.js');
- // We create a server to start doing our operations
- const checkServer = await check({ flags });
- if (checkServer) {
- if (checkServer.isWatchMode) {
- await checkServer.watch();
- return await new Promise(() => {}); // lives forever
- } else {
- const checkResult = await checkServer.check();
- return process.exit(checkResult);
- }
+ const checkServer = await check(flags);
+ if (flags.watch) {
+ return await new Promise(() => {}); // lives forever
+ } else {
+ return process.exit(checkServer ? 1 : 0);
}
- return;
}
case 'sync': {
const { sync } = await import('./sync/index.js');
diff --git a/packages/astro/src/cli/install-package.ts b/packages/astro/src/cli/install-package.ts
new file mode 100644
index 000000000..8793d9985
--- /dev/null
+++ b/packages/astro/src/cli/install-package.ts
@@ -0,0 +1,124 @@
+import boxen from 'boxen';
+import { execa } from 'execa';
+import { bold, cyan, dim, magenta } from 'kleur/colors';
+import { createRequire } from 'node:module';
+import ora from 'ora';
+import prompts from 'prompts';
+import whichPm from 'which-pm';
+import { debug, info, type LogOptions } from '../core/logger/core.js';
+
+type GetPackageOptions = {
+ skipAsk?: boolean;
+ cwd?: string;
+};
+
+export async function getPackage<T>(
+ packageName: string,
+ logging: LogOptions,
+ options: GetPackageOptions,
+ otherDeps: string[] = []
+): Promise<T | undefined> {
+ const require = createRequire(options.cwd ?? process.cwd());
+
+ let packageImport;
+ try {
+ require.resolve(packageName);
+
+ // The `require.resolve` is required as to avoid Node caching the failed `import`
+ packageImport = await import(packageName);
+ } catch (e) {
+ info(
+ logging,
+ '',
+ `To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`
+ );
+ const result = await installPackage([packageName, ...otherDeps], options, logging);
+
+ if (result) {
+ packageImport = await import(packageName);
+ } else {
+ return undefined;
+ }
+ }
+
+ return packageImport as T;
+}
+
+function getInstallCommand(packages: string[], packageManager: string) {
+ switch (packageManager) {
+ case 'npm':
+ return { pm: 'npm', command: 'install', flags: [], dependencies: packages };
+ case 'yarn':
+ return { pm: 'yarn', command: 'add', flags: [], dependencies: packages };
+ case 'pnpm':
+ return { pm: 'pnpm', command: 'add', flags: [], dependencies: packages };
+ default:
+ return null;
+ }
+}
+
+async function installPackage(
+ packageNames: string[],
+ options: GetPackageOptions,
+ logging: LogOptions
+): Promise<boolean> {
+ const cwd = options.cwd ?? process.cwd();
+ const packageManager = (await whichPm(cwd)).name ?? 'npm';
+ const installCommand = getInstallCommand(packageNames, packageManager);
+
+ if (!installCommand) {
+ return false;
+ }
+
+ const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command}${[
+ '',
+ ...installCommand.flags,
+ ].join(' ')} ${cyan(installCommand.dependencies.join(' '))}`;
+ const message = `\n${boxen(coloredOutput, {
+ margin: 0.5,
+ padding: 0.5,
+ borderStyle: 'round',
+ })}\n`;
+ info(
+ logging,
+ null,
+ `\n ${magenta('Astro will run the following command:')}\n ${dim(
+ 'If you skip this step, you can always run it yourself later'
+ )}\n${message}`
+ );
+
+ let response;
+ if (options.skipAsk) {
+ response = true;
+ } else {
+ response = (
+ await prompts({
+ type: 'confirm',
+ name: 'askToContinue',
+ message: 'Continue?',
+ initial: true,
+ })
+ ).askToContinue;
+ }
+
+ if (Boolean(response)) {
+ const spinner = ora('Installing dependencies...').start();
+ try {
+ await execa(
+ installCommand.pm,
+ [installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
+ { cwd: cwd }
+ );
+ spinner.succeed();
+
+ return true;
+ } catch (err) {
+ debug('add', 'Error installing dependencies', err);
+ spinner.fail();
+
+ return false;
+ }
+ } else {
+ return false;
+ }
+}