Skip to content

⬅️ Back to Table of Contents

📄 createLinter

📊 Analysis Summary

Metric Count
🔧 Functions 8
📦 Imports 19
📐 Interfaces 1

📚 Table of Contents

🛠️ File Location:

📂 packages/website/src/components/linter/createLinter.ts

📦 Imports

Name Source
JSONSchema @typescript-eslint/utils
TSESTree @typescript-eslint/utils
FlatConfig @typescript-eslint/utils/ts-eslint
Linter @typescript-eslint/utils/ts-eslint
SourceType @typescript-eslint/utils/ts-eslint
ErrorGroup ../../../../website/src/components/types
TabType ../../../../website/src/components/types
LinterOnLint ./types
LinterOnParse ./types
PlaygroundSystem ./types
RegisterFile ./types
WebLinterModule ./types
createCompilerOptions ../lib/createCompilerOptions
createEventsBinder ../lib/createEventsBinder
parseESLintRC ../lib/parseConfig
parseTSConfig ../lib/parseConfig
fileTypes ../options
defaultEslintLanguageConfig ./config
createParser ./createParser

Functions

createLinter(system: PlaygroundSystem, webLinterModule: WebLinterModule, vfs: typeof tsvfs, onMarkersChange: React.Dispatch< React.SetStateAction<Re…): CreateLinter

Parameters:

  • system PlaygroundSystem
  • webLinterModule WebLinterModule
  • vfs typeof tsvfs
  • onMarkersChange `React.Dispatch< React.SetStateAction>

    `

Returns: CreateLinter

Calls:

  • Object.entries
  • createParser (from ./createParser)
  • onParse.trigger
  • fileTypes.map
  • createEventsBinder (from ../lib/createEventsBinder)
  • webLinterModule.createLinter
  • Object.entries(webLinterModule.plugin.rules).forEach
  • rules.set
  • [...webLinterModule.builtinRules.entries()].forEach
  • webLinterModule.builtinRules.entries
  • console.info
  • system.readFile
  • linter.verify
  • onLint.trigger
  • String
  • linter.verifyAndFix
  • parseESLintRC (from ../lib/parseConfig)
  • configs.get
  • Array.isArray
  • eslintExtendedConfig.push
  • console.log
  • console.error
  • parseTSConfig (from ../lib/parseConfig)
  • createCompilerOptions (from ../lib/createCompilerOptions)
  • parser.updateConfig
  • e.message .trim() .split('\n') .map
  • onMarkersChange
  • Object.fromEntries
  • prev.tsconfig.map
  • Object.values
  • parser.registerFile
  • triggerLintAll
  • system.searchFiles('/input.*').forEach
  • system.watchFile
  • applyEslintConfig
  • applyTSConfig
  • configs.keys

Internal Comments:

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x8)

Code
export function createLinter(
  system: PlaygroundSystem,
  webLinterModule: WebLinterModule,
  vfs: typeof tsvfs,
  onMarkersChange: React.Dispatch<
    React.SetStateAction<Record<TabType, ErrorGroup[]>>
  >,
): CreateLinter {
  const rules: CreateLinter['rules'] = new Map();
  const configs = new Map(Object.entries(webLinterModule.configs));
  let compilerOptions: ts.CompilerOptions = {};
  const parser = createParser(
    system,
    compilerOptions,
    (filename, model): void => {
      onParse.trigger(filename, model);
    },
    webLinterModule,
    vfs,
  );

  const eslintLanguageConfig: FlatConfig.Config = {
    files: fileTypes.map(extension => `**/*${extension}`),
    languageOptions: {
      ...defaultEslintLanguageConfig,
      parser,
      parserOptions: { ...defaultEslintLanguageConfig.parserOptions },
    },
    plugins: {
      '@typescript-eslint': webLinterModule.plugin,
    },
  };

  const eslintExtendedConfig: FlatConfig.ConfigArray = [];

  const eslintRulesConfig: FlatConfig.Config = {
    rules: {},
  };

  const onLint = createEventsBinder<LinterOnLint>();
  const onParse = createEventsBinder<LinterOnParse>();
  const linter = webLinterModule.createLinter();

  Object.entries(webLinterModule.plugin.rules).forEach(([name, item]) => {
    rules.set(`@typescript-eslint/${name}`, {
      description: item.meta.docs?.description,
      name: `@typescript-eslint/${name}`,
      schema: item.meta.schema,
      url: item.meta.docs?.url,
    });
  });

  [...webLinterModule.builtinRules.entries()].forEach(([name, item]) => {
    rules.set(name, {
      description: item.meta.docs?.description,
      name,
      schema: item.meta.schema,
      url: item.meta.docs?.url,
    });
  });

  const triggerLint = (filename: string): void => {
    console.info('[Editor] linting triggered for file', filename);
    const code = system.readFile(filename) ?? '\n';
    try {
      const messages = linter.verify(
        code,
        [eslintLanguageConfig, ...eslintExtendedConfig, eslintRulesConfig],
        filename,
      );
      onLint.trigger(filename, messages);
    } catch (e) {
      const lintMessage: Linter.LintMessage = {
        column: 1,
        line: 1,
        message: String(e instanceof Error ? `${e.message}\n\n${e.stack}` : e),
        ruleId: '',
        severity: 2,
        source: 'eslint',
      };
      if (typeof e === 'object' && e && 'currentNode' in e) {
        const node = e.currentNode as TSESTree.Node;
        lintMessage.column = node.loc.start.column + 1;
        lintMessage.line = node.loc.start.line;
        lintMessage.endColumn = node.loc.end.column + 1;
        lintMessage.endLine = node.loc.end.line;
      }
      onLint.trigger(filename, [lintMessage]);
    }
  };

  const triggerFix = (filename: string): Linter.FixReport | undefined => {
    const code = system.readFile(filename);
    if (code) {
      return linter.verifyAndFix(
        code,
        [eslintLanguageConfig, ...eslintExtendedConfig, eslintRulesConfig],
        {
          filename,
          fix: true,
        },
      );
    }
    return undefined;
  };

  const updateParserOptions = (sourceType?: SourceType): void => {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    eslintLanguageConfig.languageOptions!.parserOptions!.sourceType =
      sourceType ?? 'module';
  };

  const applyEslintConfig = (fileName: string): void => {
    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = parseESLintRC(file);
      eslintRulesConfig.rules = parsed.rules;
      for (const extendsName of parsed.extends) {
        const maybeConfig = configs.get(extendsName);
        if (maybeConfig) {
          if (Array.isArray(maybeConfig)) {
            eslintExtendedConfig.push(...maybeConfig);
          } else {
            eslintExtendedConfig.push(maybeConfig);
          }
        }
      }
      console.log('[Editor] Updating', fileName, [
        eslintLanguageConfig,
        ...eslintExtendedConfig,
        eslintRulesConfig,
      ]);
    } catch (e) {
      console.error(e);
    }
  };

  const applyTSConfig = (fileName: string): void => {
    let error: ErrorGroup | null = null;

    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = parseTSConfig(file).compilerOptions;
      compilerOptions = createCompilerOptions(parsed);
      console.log('[Editor] Updating', fileName, compilerOptions);
      parser.updateConfig(compilerOptions);
    } catch (e) {
      if (e instanceof Error) {
        error = {
          group: 'TypeScript',
          items: e.message
            .trim()
            .split('\n')
            .map((message: string) => {
              return {
                message,
                severity: 8, // MarkerSeverity.Error
              };
            }),
          uri: undefined,
        };
      }
    } finally {
      onMarkersChange(prev => {
        const activeTabErrors = Object.fromEntries(
          prev.tsconfig.map(error => [error.group, error]),
        );

        if (error) {
          activeTabErrors.TypeScript = error;
        } else {
          delete activeTabErrors.TypeScript;
        }

        return {
          ...prev,
          tsconfig: Object.values(activeTabErrors),
        };
      });
    }
  };

  const registerFile = (fileName: string, code: string) => {
    parser.registerFile(fileName, code);
    triggerLintAll();
  };

  const triggerLintAll = (): void => {
    system.searchFiles('/input.*').forEach(triggerLint);
  };

  system.watchFile('/input.*', triggerLint);
  system.watchFile('/.eslintrc', filename => {
    applyEslintConfig(filename);
    triggerLintAll();
  });
  system.watchFile('/tsconfig.json', filename => {
    applyTSConfig(filename);
    triggerLintAll();
  });

  applyEslintConfig('/.eslintrc');
  applyTSConfig('/tsconfig.json');

  return {
    configs: [...configs.keys()],
    onLint: onLint.register,
    onParse: onParse.register,
    registerFile,
    rules,
    triggerFix,
    triggerLint,
    updateParserOptions,
  };
}

Internal helpers

Declared inside another function in this file.

triggerLint(filename: string): void

Parameters:

  • filename string

Returns: void

Calls:

  • console.info
  • system.readFile
  • linter.verify
  • onLint.trigger
  • String
Code
(filename: string): void => {
    console.info('[Editor] linting triggered for file', filename);
    const code = system.readFile(filename) ?? '\n';
    try {
      const messages = linter.verify(
        code,
        [eslintLanguageConfig, ...eslintExtendedConfig, eslintRulesConfig],
        filename,
      );
      onLint.trigger(filename, messages);
    } catch (e) {
      const lintMessage: Linter.LintMessage = {
        column: 1,
        line: 1,
        message: String(e instanceof Error ? `${e.message}\n\n${e.stack}` : e),
        ruleId: '',
        severity: 2,
        source: 'eslint',
      };
      if (typeof e === 'object' && e && 'currentNode' in e) {
        const node = e.currentNode as TSESTree.Node;
        lintMessage.column = node.loc.start.column + 1;
        lintMessage.line = node.loc.start.line;
        lintMessage.endColumn = node.loc.end.column + 1;
        lintMessage.endLine = node.loc.end.line;
      }
      onLint.trigger(filename, [lintMessage]);
    }
  }

triggerFix(filename: string): Linter.FixReport | undefined

Parameters:

  • filename string

Returns: Linter.FixReport | undefined

Calls:

  • system.readFile
  • linter.verifyAndFix
Code
(filename: string): Linter.FixReport | undefined => {
    const code = system.readFile(filename);
    if (code) {
      return linter.verifyAndFix(
        code,
        [eslintLanguageConfig, ...eslintExtendedConfig, eslintRulesConfig],
        {
          filename,
          fix: true,
        },
      );
    }
    return undefined;
  }

updateParserOptions(sourceType: SourceType): void

Parameters:

  • sourceType SourceType

Returns: void

Internal Comments:

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x8)

Code
(sourceType?: SourceType): void => {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    eslintLanguageConfig.languageOptions!.parserOptions!.sourceType =
      sourceType ?? 'module';
  }

applyEslintConfig(fileName: string): void

Parameters:

  • fileName string

Returns: void

Calls:

  • system.readFile
  • parseESLintRC (from ../lib/parseConfig)
  • configs.get
  • Array.isArray
  • eslintExtendedConfig.push
  • console.log
  • console.error
Code
(fileName: string): void => {
    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = parseESLintRC(file);
      eslintRulesConfig.rules = parsed.rules;
      for (const extendsName of parsed.extends) {
        const maybeConfig = configs.get(extendsName);
        if (maybeConfig) {
          if (Array.isArray(maybeConfig)) {
            eslintExtendedConfig.push(...maybeConfig);
          } else {
            eslintExtendedConfig.push(maybeConfig);
          }
        }
      }
      console.log('[Editor] Updating', fileName, [
        eslintLanguageConfig,
        ...eslintExtendedConfig,
        eslintRulesConfig,
      ]);
    } catch (e) {
      console.error(e);
    }
  }

applyTSConfig(fileName: string): void

Parameters:

  • fileName string

Returns: void

Calls:

  • system.readFile
  • parseTSConfig (from ../lib/parseConfig)
  • createCompilerOptions (from ../lib/createCompilerOptions)
  • console.log
  • parser.updateConfig
  • e.message .trim() .split('\n') .map
  • onMarkersChange
  • Object.fromEntries
  • prev.tsconfig.map
  • Object.values
Code
(fileName: string): void => {
    let error: ErrorGroup | null = null;

    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = parseTSConfig(file).compilerOptions;
      compilerOptions = createCompilerOptions(parsed);
      console.log('[Editor] Updating', fileName, compilerOptions);
      parser.updateConfig(compilerOptions);
    } catch (e) {
      if (e instanceof Error) {
        error = {
          group: 'TypeScript',
          items: e.message
            .trim()
            .split('\n')
            .map((message: string) => {
              return {
                message,
                severity: 8, // MarkerSeverity.Error
              };
            }),
          uri: undefined,
        };
      }
    } finally {
      onMarkersChange(prev => {
        const activeTabErrors = Object.fromEntries(
          prev.tsconfig.map(error => [error.group, error]),
        );

        if (error) {
          activeTabErrors.TypeScript = error;
        } else {
          delete activeTabErrors.TypeScript;
        }

        return {
          ...prev,
          tsconfig: Object.values(activeTabErrors),
        };
      });
    }
  }

registerFile(fileName: string, code: string): void

Parameters:

  • fileName string
  • code string

Returns: void

Calls:

  • parser.registerFile
  • triggerLintAll
Code
(fileName: string, code: string) => {
    parser.registerFile(fileName, code);
    triggerLintAll();
  }

triggerLintAll(): void

Returns: void

Calls:

  • system.searchFiles('/input.*').forEach
Code
(): void => {
    system.searchFiles('/input.*').forEach(triggerLint);
  }

Interfaces

CreateLinter

Interface Code
export interface CreateLinter {
  configs: string[];
  onLint(cb: LinterOnLint): () => void;
  onParse(cb: LinterOnParse): () => void;
  rules: Map<
    string,
    {
      description?: string;
      name: string;
      schema: JSONSchema.JSONSchema4 | readonly JSONSchema.JSONSchema4[];
      url?: string;
    }
  >;
  triggerFix(filename: string): Linter.FixReport | undefined;
  triggerLint(filename: string): void;
  updateParserOptions(sourceType?: SourceType): void;
  registerFile: RegisterFile;
}

Properties

Name Type Optional Description
configs string[] not shown
rules Map< string, { description?: string; name: string; schema: JSONSchema.JSONSch... not shown
registerFile RegisterFile not shown

Generated by Syntax Scribe