Skip to content

⬅️ Back to Table of Contents

📄 createLinter.ts

📊 Analysis Summary

Metric Count
🔧 Functions 8
📦 Imports 16
📊 Variables & Constants 12
📐 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
ClassicConfig @typescript-eslint/utils/ts-eslint
Linter @typescript-eslint/utils/ts-eslint
SourceType @typescript-eslint/utils/ts-eslint
LinterOnLint ./types
LinterOnParse ./types
PlaygroundSystem ./types
WebLinterModule ./types
createCompilerOptions ../lib/createCompilerOptions
createEventsBinder ../lib/createEventsBinder
parseESLintRC ../lib/parseConfig
parseTSConfig ../lib/parseConfig
defaultEslintConfig ./config
PARSER_NAME ./config
createParser ./createParser

Variables & Constants

Name Type Kind Value Exported
rules CreateLinter['rules'] const new Map()
configs Map<string, ClassicConfig.Config> const new Map(Object.entries(webLinterModule.configs))
compilerOptions ts.CompilerOptions let/var {}
eslintConfig ClassicConfig.Config const { ...defaultEslintConfig }
code any const system.readFile(filename) ?? '\n'
lintMessage Linter.LintMessage const `{
column: 1,
line: 1,
message: String(e instanceof Error ? e.stack : e),
nodeType: '',
ruleId: '',
severity: 2,
source: 'eslint',
}`
node TSESTree.Node const e.currentNode as TSESTree.Node
config { rules: {}; } const { rules: {} }
cfgExtends any const `Array.isArray(cfg.extends)
? cfg.extends
: [cfg.extends]`
file any const system.readFile(fileName) ?? '{}'
file any const system.readFile(fileName) ?? '{}'
parsed CompilerFlags const parseTSConfig(file).compilerOptions

Functions

createLinter(system: PlaygroundSystem, webLinterModule: WebLinterModule, vfs: typeof tsvfs): CreateLinter

Code
export function createLinter(
  system: PlaygroundSystem,
  webLinterModule: WebLinterModule,
  vfs: typeof tsvfs,
): CreateLinter {
  const rules: CreateLinter['rules'] = new Map();
  const configs = new Map(Object.entries(webLinterModule.configs));
  let compilerOptions: ts.CompilerOptions = {};
  const eslintConfig: ClassicConfig.Config = { ...defaultEslintConfig };

  const onLint = createEventsBinder<LinterOnLint>();
  const onParse = createEventsBinder<LinterOnParse>();

  const linter = webLinterModule.createLinter();

  const parser = createParser(
    system,
    compilerOptions,
    (filename, model): void => {
      onParse.trigger(filename, model);
    },
    webLinterModule,
    vfs,
  );

  linter.defineParser(PARSER_NAME, parser);

  linter.getRules().forEach((item, name) => {
    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, eslintConfig, filename);
      onLint.trigger(filename, messages);
    } catch (e) {
      const lintMessage: Linter.LintMessage = {
        column: 1,
        line: 1,
        message: String(e instanceof Error ? e.stack : e),
        nodeType: '',
        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, eslintConfig, {
        filename,
        fix: true,
      });
    }
    return undefined;
  };

  const updateParserOptions = (sourceType?: SourceType): void => {
    eslintConfig.parserOptions ??= {};
    eslintConfig.parserOptions.sourceType = sourceType ?? 'module';
  };

  const resolveEslintConfig = (
    cfg: Partial<ClassicConfig.Config>,
  ): ClassicConfig.Config => {
    const config = { rules: {} };
    if (cfg.extends) {
      const cfgExtends = Array.isArray(cfg.extends)
        ? cfg.extends
        : [cfg.extends];
      for (const extendsName of cfgExtends) {
        const maybeConfig = configs.get(extendsName);
        if (maybeConfig) {
          const resolved = resolveEslintConfig(maybeConfig);
          if (resolved.rules) {
            Object.assign(config.rules, resolved.rules);
          }
        }
      }
    }
    if (cfg.rules) {
      Object.assign(config.rules, cfg.rules);
    }
    return config;
  };

  const applyEslintConfig = (fileName: string): void => {
    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = resolveEslintConfig(parseESLintRC(file));
      eslintConfig.rules = parsed.rules;
      console.log('[Editor] Updating', fileName, eslintConfig);
    } catch (e) {
      console.error(e);
    }
  };

  const applyTSConfig = (fileName: string): void => {
    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) {
      console.error(e);
    }
  };

  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,
    rules,
    triggerFix,
    triggerLint,
    updateParserOptions,
  };
}
  • Parameters:
  • system: PlaygroundSystem
  • webLinterModule: WebLinterModule
  • vfs: typeof tsvfs
  • Return Type: CreateLinter
  • Calls:
  • Object.entries
  • createEventsBinder (from ../lib/createEventsBinder)
  • webLinterModule.createLinter
  • createParser (from ./createParser)
  • onParse.trigger
  • linter.defineParser
  • linter.getRules().forEach
  • rules.set
  • console.info
  • system.readFile
  • linter.verify
  • onLint.trigger
  • String
  • linter.verifyAndFix
  • Array.isArray
  • configs.get
  • resolveEslintConfig
  • Object.assign
  • parseESLintRC (from ../lib/parseConfig)
  • console.log
  • console.error
  • parseTSConfig (from ../lib/parseConfig)
  • createCompilerOptions (from ../lib/createCompilerOptions)
  • parser.updateConfig
  • system.searchFiles('/input.*').forEach
  • system.watchFile
  • applyEslintConfig
  • triggerLintAll
  • applyTSConfig
  • configs.keys

triggerLint(filename: string): void

Code
(filename: string): void => {
    console.info('[Editor] linting triggered for file', filename);
    const code = system.readFile(filename) ?? '\n';
    try {
      const messages = linter.verify(code, eslintConfig, filename);
      onLint.trigger(filename, messages);
    } catch (e) {
      const lintMessage: Linter.LintMessage = {
        column: 1,
        line: 1,
        message: String(e instanceof Error ? e.stack : e),
        nodeType: '',
        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]);
    }
  }
  • Parameters:
  • filename: string
  • Return Type: void
  • Calls:
  • console.info
  • system.readFile
  • linter.verify
  • onLint.trigger
  • String

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

Code
(filename: string): Linter.FixReport | undefined => {
    const code = system.readFile(filename);
    if (code) {
      return linter.verifyAndFix(code, eslintConfig, {
        filename,
        fix: true,
      });
    }
    return undefined;
  }
  • Parameters:
  • filename: string
  • Return Type: Linter.FixReport | undefined
  • Calls:
  • system.readFile
  • linter.verifyAndFix

updateParserOptions(sourceType: SourceType): void

Code
(sourceType?: SourceType): void => {
    eslintConfig.parserOptions ??= {};
    eslintConfig.parserOptions.sourceType = sourceType ?? 'module';
  }
  • Parameters:
  • sourceType: SourceType
  • Return Type: void

resolveEslintConfig(cfg: Partial<ClassicConfig.Config>): ClassicConfig.Config

Code
(
    cfg: Partial<ClassicConfig.Config>,
  ): ClassicConfig.Config => {
    const config = { rules: {} };
    if (cfg.extends) {
      const cfgExtends = Array.isArray(cfg.extends)
        ? cfg.extends
        : [cfg.extends];
      for (const extendsName of cfgExtends) {
        const maybeConfig = configs.get(extendsName);
        if (maybeConfig) {
          const resolved = resolveEslintConfig(maybeConfig);
          if (resolved.rules) {
            Object.assign(config.rules, resolved.rules);
          }
        }
      }
    }
    if (cfg.rules) {
      Object.assign(config.rules, cfg.rules);
    }
    return config;
  }
  • Parameters:
  • cfg: Partial<ClassicConfig.Config>
  • Return Type: ClassicConfig.Config
  • Calls:
  • Array.isArray
  • configs.get
  • resolveEslintConfig
  • Object.assign

applyEslintConfig(fileName: string): void

Code
(fileName: string): void => {
    try {
      const file = system.readFile(fileName) ?? '{}';
      const parsed = resolveEslintConfig(parseESLintRC(file));
      eslintConfig.rules = parsed.rules;
      console.log('[Editor] Updating', fileName, eslintConfig);
    } catch (e) {
      console.error(e);
    }
  }
  • Parameters:
  • fileName: string
  • Return Type: void
  • Calls:
  • system.readFile
  • resolveEslintConfig
  • parseESLintRC (from ../lib/parseConfig)
  • console.log
  • console.error

applyTSConfig(fileName: string): void

Code
(fileName: string): void => {
    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) {
      console.error(e);
    }
  }
  • Parameters:
  • fileName: string
  • Return Type: void
  • Calls:
  • system.readFile
  • parseTSConfig (from ../lib/parseConfig)
  • createCompilerOptions (from ../lib/createCompilerOptions)
  • console.log
  • parser.updateConfig
  • console.error

triggerLintAll(): void

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

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;
}

Properties

Name Type Optional Description
configs string[]
rules `Map<
string,
{
description?: string;
name: string;
schema: JSONSchema.JSONSchema4 readonly JSONSchema.JSONSchema4[];
url?: string;
}
>`