Repository: postcss/postcss (TypeScript)

Issue:
Can't discriminate `node.parent` node's type in Typescript
The `node.parent` value returns a `ContainerWithChildren` type. This interface extends `Node` directly, so it is not possible to determine at compile-type which type of node is the `parent` node just by looking at `type`. It is only possible to do at runtime, and this makes the interface not very useful for Typescript.

Example TS code [[stackblitz](https://stackblitz.com/edit/node-typescript-playground-wznqgdmj?file=src%2Findex.ts)]:
```ts
import postcss from 'postcss';

const { root } = postcss([]).process('@a{.b{}}').sync();
const [atRule] = root.nodes;
if (atRule.type !== 'atrule') throw new Error('not at-rule');
const [rule] = atRule.nodes ?? [];
if (rule.type !== 'rule') throw new Error('not rule');
const { parent } = rule;

switch (parent?.type) {
  case 'rule':
  case 'comment':
  case 'atrule':
  case 'decl':
  case 'root':
  case undefined:
    console.log('yay');
  default:
    // This fails because `parent` is of type `ContainerWithChildren` which is
    // an interface that extends `Node` rather than being a sumtype.
    const impossible: never = parent;
}
```

Arguably, the `ContainerWithChildren` interface should not `extend Container`. Instead it should be declared something like:

```ts
type ContainerWithChildren<T> = (Root | AtRule | Rule) & Container<T>;
```

(I haven't verified this type, but it should be something along these lines)


Discussion:
Perhaps this?

```ts
type ContainerWithChildren<T> = (Root | AtRule | Rule) & {
  nodes: T[];
};
```

?



Patch:
diff --git a/lib/container.d.ts b/lib/container.d.ts
index 9569f343b..c2b310b6b 100644
--- a/lib/container.d.ts
+++ b/lib/container.d.ts
@@ -2,14 +2,17 @@ import AtRule from './at-rule.js'
 import Comment from './comment.js'
 import Declaration from './declaration.js'
 import Node, { ChildNode, ChildProps, NodeProps } from './node.js'
+import { Root } from './postcss.js'
 import Rule from './rule.js'
 
 declare namespace Container {
-  export class ContainerWithChildren<
-    Child extends Node = ChildNode
-  > extends Container_<Child> {
+  export type ContainerWithChildren<Child extends Node = ChildNode> = {
     nodes: Child[]
-  }
+  } & (
+    | AtRule
+    | Root
+    | Rule
+  )
 
   export interface ValueOptions {
     /**


Test patch:
diff --git a/test/types.ts b/test/types.ts
index 316be4ee0..4f7b861d8 100644
--- a/test/types.ts
+++ b/test/types.ts
@@ -1,4 +1,5 @@
 import postcss, { Document, PluginCreator } from '../lib/postcss.js'
+import { RootRaws } from '../lib/root.js'
 
 const plugin: PluginCreator<string> = prop => {
   return {
@@ -30,4 +31,34 @@ function parseMarkdown(): Document {
 let doc = postcss().process('a{}', { parser: parseMarkdown }).root
 console.log(doc.toString())
 
+function parentCanNarrowType(): never | void {
+  let atRule = postcss.parse('@a{b{}}').first
+  if (atRule?.type !== 'atrule') return
+  let rule = atRule.first
+  if (rule?.type !== 'rule') return
+  let parent = rule.parent
+  switch (parent?.type) {
+    case undefined:
+      console.log('ok')
+      break
+    case 'atrule':
+      console.log(parent.params)
+      break
+    case 'root':
+      {
+        let raws: RootRaws = parent.raws
+        console.log(raws)
+      }
+      break
+    case 'rule':
+      console.log(rule.selector)
+      break
+    default: {
+      let exhaustiveCheck: never = parent
+      return exhaustiveCheck
+    }
+  }
+}
+parentCanNarrowType()
+
 export default plugin